From 76d6ada6cba939f85bf8dda9b74b18ced426c239 Mon Sep 17 00:00:00 2001 From: Christian Koort Date: Fri, 9 Oct 2026 11:44:58 +0200 Subject: [PATCH] Fix Go SDK generation for metamodel V3.2 We convert read-only verification slices such as ``[]ISomeItem`` to ``[]IAbstractItem`` so V3.2 verification compiles. We also escape encoded regex metacharacters such as ``\x5B`` so valid reference identifiers pass verification. We tolerate whitespace between XML elements and compare decoded round-trip values, allowing equivalent forms such as ``42.0`` and ``42``. We add regression coverage and regenerate affected goldens. Validation: Black, ssort, mypy; 49 unit/golden tests; 38 Go cases; 1,716 V3.2 SDK tests with corrected testdatagen fixtures. --- .../golang/lib/_generate_verification.py | 11 +- .../golang/lib/_generate_xml_common.py | 5 + aas_core_codegen/golang/main.py | 2 +- .../_generate_xmlization_test_common_test.py | 162 ++---------------- ...e_xmlization_test_concrete_classes_test.py | 2 +- aas_core_codegen/golang/transpilation.py | 50 ++++++ .../common_meta_models/list_of_classes.py | 26 ++- .../include/dummy/verification.hpp | 36 ++++ .../expected_output/src/verification.cpp | 62 +++++++ .../expected_output/dummy/verification.cs | 42 +++++ .../internal/xmlcommon/xmlcommon.go | 5 + .../verification/verification.go | 4 +- .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 76 ++++---- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 8 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 6 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 4 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 4 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 12 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 4 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 10 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 6 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 18 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../verification/verification.go | 57 ++++++ .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 8 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 12 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 6 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 4 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 4 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 4 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 6 +- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 20 +-- .../internal/xmlcommon/xmlcommon.go | 5 + .../xmlization/test/common_test.go | 148 ++-------------- .../xmlization/test/concrete_classes_test.go | 2 +- .../java/dummy/verification/Verification.java | 35 ++++ .../expected_output/dummy/verification.py | 44 +++++ .../expected_output/src/verification.ts | 45 +++++ dev/tests/golang/test_verification.py | 35 ++++ 130 files changed, 1264 insertions(+), 5420 deletions(-) create mode 100644 dev/tests/golang/test_verification.py diff --git a/aas_core_codegen/golang/lib/_generate_verification.py b/aas_core_codegen/golang/lib/_generate_verification.py index f78d9d4d1..d08bef62d 100644 --- a/aas_core_codegen/golang/lib/_generate_verification.py +++ b/aas_core_codegen/golang/lib/_generate_verification.py @@ -116,16 +116,7 @@ class RegexRenderer(parse_retree.Renderer): def char_to_str_and_escape_or_encode_if_necessary( self, node: parse_retree.Char, escaping: Mapping[str, str] ) -> List[Union[str, parse_tree.FormattedValue]]: - if not node.explicitly_encoded: - escaped = escaping.get(node.character, None) - if escaped is not None: - result: List[Union[str, parse_tree.FormattedValue]] = [escaped] - else: - result = [node.character] - - return result - - return [node.character] + return [escaping.get(node.character, node.character)] _REGEX_RENDERER = RegexRenderer() diff --git a/aas_core_codegen/golang/lib/_generate_xml_common.py b/aas_core_codegen/golang/lib/_generate_xml_common.py index c2c31679b..08015819c 100644 --- a/aas_core_codegen/golang/lib/_generate_xml_common.py +++ b/aas_core_codegen/golang/lib/_generate_xml_common.py @@ -945,6 +945,11 @@ def _generate_read_element_dispatched() -> Stripped: {II}return {I}}} +{I}current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) +{I}if err != nil {{ +{II}return +{I}}} + {I}err = checkEndElementInNamespace(current, local, ns) {I}if err != nil {{ {II}return diff --git a/aas_core_codegen/golang/main.py b/aas_core_codegen/golang/main.py index b3884dd36..0c0377965 100644 --- a/aas_core_codegen/golang/main.py +++ b/aas_core_codegen/golang/main.py @@ -365,7 +365,7 @@ def execute(context: run.Context, stdout: TextIO, stderr: TextIO) -> int: if errors is not None: run.write_error_report( - message=f"Failed to generate {rel_path} " + message=f"Failed to generate {rel_path.as_posix()} " f"based on {context.model_path}", errors=[ context.lineno_columner.error_message(error) for error in errors diff --git a/aas_core_codegen/golang/tests/_generate_xmlization_test_common_test.py b/aas_core_codegen/golang/tests/_generate_xmlization_test_common_test.py index 58fdc1750..1732be045 100644 --- a/aas_core_codegen/golang/tests/_generate_xmlization_test_common_test.py +++ b/aas_core_codegen/golang/tests/_generate_xmlization_test_common_test.py @@ -35,13 +35,14 @@ def generate(repo_url: Stripped) -> str: Stripped( f"""\ import ( +{I}"encoding/xml" {I}"fmt" {I}"os" {I}"path/filepath" -{I}"regexp" {I}"strings" {I}"testing" {I}ourtesting "{repo_url}/ourtesting" +{I}ourtypes "{repo_url}/types" {I}ourxmlization "{repo_url}/xmlization" )""" ), @@ -86,166 +87,29 @@ def generate(repo_url: Stripped) -> str: {II}return {I}}} {I}return -}}""" - ), - Stripped( - """\ -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174""" - ), - Stripped( - """\ -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\\w+(?:\\s[^>]*[^/>])?)>`)""" - ), - Stripped( - f"""\ -func forceSelfClosingTags(text string) string {{ -{I}b := []byte(text) -{I}emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - -{I}if len(emptyTagIdxs) == 0 {{ -{II}return text -{I}}} - -{I}// NOTE (mristin): -{I}// The matches are walked in order, and the pieces of `b` between them are -{I}// copied over as they are. Mind that `nb` starts out empty and that `b` is -{I}// only ever *read*: an earlier version appended into `b[0:idx[0]]`, which -{I}// writes into `b`'s own backing array, so a document with more than one -{I}// empty tag came out garbled -- every turn of the loop then indexed into -{I}// a `b` which the previous turn had already overwritten. -{I}var nb []byte -{I}previous := 0 - -{I}for _, idx := range emptyTagIdxs {{ -{II}// Copy everything since the previous match, then the tag itself -- -{II}// its name and its attributes -- as a self-closing one. -{II}nb = append(nb, b[previous:idx[0]]...) -{II}nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - -{II}previous = idx[1] -{I}}} - -{I}// Finally, copy everything after the last match. -{I}nb = append(nb, b[previous:]...) - -{I}return string(nb) -}}""" - ), - Stripped( - """\ -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\\s+<`)""" - ), - Stripped( - f"""\ -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string {{ -{I}return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -}}""" - ), - Stripped( - """\ -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\\s+`)""" - ), - Stripped( - f"""\ -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string {{ -{I}result := text -{I}result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") -{I}result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") -{I}result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") -{I}return result }}""" ), Stripped( f"""\ -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( {I}t *testing.T, -{I}that string, +{I}that ourtypes.IClass, {I}other string, {I}source string, ) (ok bool) {{ -{I}ok = true - -{I}// Remove carriers to avoid problems between Windows, Posix and MacOS -{I}canonicalThat := strings.ReplaceAll(that, "\\r", "") -{I}canonicalOther := strings.ReplaceAll(other, "\\r", "") - -{I}canonicalThat = strings.TrimSpace(canonicalThat) -{I}canonicalOther = strings.TrimSpace(canonicalOther) - -{I}canonicalThat = forceSelfClosingTags(canonicalThat) -{I}canonicalOther = forceSelfClosingTags(canonicalOther) - -{I}// NOTE (mristin): -{I}// The following hack is SUPER ugly and unsafe! However, it works. Given Go's -{I}// limited support for XML, we gave up on a safer approach :(. We tested -{I}// the following approaches before applying this hack: -{I}// * A round-trip over `encoding/xml`. Failed due to -{I}// https://github.com/golang/go/issues/13400. -{I}// * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the -{I}// element content still has not been de-escaped or consistently escaped in -{I}// a round trip. - -{I}canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") -{I}canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - -{I}canonicalThat = removeWhitespaceBetweenTags(canonicalThat) -{I}canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - -{I}canonicalThat = removeWhitespaceWithinTags(canonicalThat) -{I}canonicalOther = removeWhitespaceWithinTags(canonicalOther) - -{I}thatLines := strings.Split(canonicalThat, "\\n") -{I}otherLines := strings.Split(canonicalOther, "\\n") - -{I}if canonicalThat != canonicalOther {{ -{II}b := new(strings.Builder) -{II}minLines := len(thatLines) -{II}if minLines > len(otherLines) {{ -{III}minLines = len(otherLines) -{II}}} -{II}for i := 0; i < minLines; i++ {{ -{III}if thatLines[i] == otherLines[i] {{ -{IIII}b.WriteString(fmt.Sprintf(" %s\\n", thatLines[i])) -{III}}} else {{ -{IIII}b.WriteString(fmt.Sprintf("ORIGINAL %s\\n", thatLines[i])) -{IIII}b.WriteString(fmt.Sprintf("SERIALIZED %s\\n", otherLines[i])) -{IIII}break -{III}}} -{II}}} +{I}decoder := xml.NewDecoder(strings.NewReader(other)) +{I}roundTripped, err := ourxmlization.Unmarshal(decoder) +{I}if !assertNoDeserializationError(t, err, source) {{ +{II}return false +{I}}} -{II}ok = false -{II}t.Fatalf( -{III}"The canonicalized XML serialization of the de-serialized instance "+ -{IIII}"from %s does not equal the canonicalized original XML document:\\n"+ -{IIII}"%s", -{III}source, b.String(), -{II}) -{II}return +{I}if !ourtesting.DeepEqual(that, roundTripped) {{ +{II}t.Fatalf("XML round-trip changed the instance from %s", source) +{II}return false {I}}} -{I}return +{I}return true }}""" ), Stripped( diff --git a/aas_core_codegen/golang/tests/_generate_xmlization_test_concrete_classes_test.py b/aas_core_codegen/golang/tests/_generate_xmlization_test_concrete_classes_test.py index 157928e83..b5018bf47 100644 --- a/aas_core_codegen/golang/tests/_generate_xmlization_test_concrete_classes_test.py +++ b/aas_core_codegen/golang/tests/_generate_xmlization_test_concrete_classes_test.py @@ -90,7 +90,7 @@ def _generate_for_cls(cls: intermediate.ConcreteClass) -> List[Stripped]: {II}ok = assertSerializationEqualsDeserialization( {III}t, -{III}text, +{III}deserialized, {III}roundTrip, {III}pth, {II}) diff --git a/aas_core_codegen/golang/transpilation.py b/aas_core_codegen/golang/transpilation.py index 99614f58a..ff0a85e45 100644 --- a/aas_core_codegen/golang/transpilation.py +++ b/aas_core_codegen/golang/transpilation.py @@ -1133,6 +1133,56 @@ def transform_function_call( for arg_node, arg in zip(node.args, args) ] + for i, (arg_node, argument) in enumerate( + zip(node.args, func_type.func.arguments) + ): + source_type = intermediate_type_inference.beneath_optional( + self.type_map[arg_node] + ) + target_type = intermediate.beneath_optional(argument.type_annotation) + if not ( + isinstance(func_type.func, intermediate.TranspilableVerification) + and not argument.mutable + and isinstance( + source_type, intermediate_type_inference.ListTypeAnnotation + ) + and isinstance(target_type, intermediate.ListTypeAnnotation) + and isinstance( + source_type.items, intermediate_type_inference.OurTypeAnnotation + ) + and isinstance(target_type.items, intermediate.OurTypeAnnotation) + and isinstance(source_type.items.our_type, intermediate.Class) + and isinstance(target_type.items.our_type, intermediate.Class) + and source_type.items.our_type is not target_type.items.our_type + and source_type.items.our_type.is_subclass_of( + target_type.items.our_type + ) + ): + continue + + # NOTE (mristin): + # Go slices are invariant even when their item interfaces inherit. + # We convert only mismatching read-only arguments, preserving nil. + source_slice = self._our_type_name( + golang_naming.interface_name(source_type.items.our_type.name) + ) + target_slice = self._our_type_name( + golang_naming.interface_name(target_type.items.our_type.name) + ) + args[i] = Stripped( + f"""\ +func(items []{source_slice}) []{target_slice} {{ +{I}if items == nil {{ +{II}return nil +{I}}} +{I}result := make([]{target_slice}, len(items)) +{I}for i, item := range items {{ +{II}result[i] = item +{I}}} +{I}return result +}}({args[i]})""" + ) + args_joined = ", ".join(args) # Apply heuristic for breaking the lines diff --git a/dev/test_data/common_meta_models/list_of_classes.py b/dev/test_data/common_meta_models/list_of_classes.py index e43fb6404..a78e6db8c 100644 --- a/dev/test_data/common_meta_models/list_of_classes.py +++ b/dev/test_data/common_meta_models/list_of_classes.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import List +from typing import List, Optional, Sequence from icontract import invariant @@ -17,6 +17,30 @@ def __init__(self, name: str) -> None: self.name = name +@verification +def items_are_some(items: Sequence[Abstract_item]) -> bool: + """Check a read-only list of base-class instances.""" + return all(isinstance(item, Some_item) for item in items) + + +@verification +def some_items_are_some(items: Sequence[Some_item]) -> bool: + """Pass a list of descendants to a base-class list parameter.""" + return items_are_some(items) + + +@verification +def optional_some_items_are_some(items: Optional[Sequence[Some_item]]) -> bool: + """Preserve an optional descendant list passed to an optional parameter.""" + return optional_items_are_some(items) + + +@verification +def optional_items_are_some(items: Optional[Sequence[Abstract_item]]) -> bool: + """Check a possibly missing read-only list of base-class instances.""" + return items is None or items_are_some(items) + + class Another_item(Abstract_item): serial_number: int diff --git a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp index 0dfe6262e..228ced3dc 100644 --- a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp +++ b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp @@ -212,6 +212,42 @@ class RecursiveVerification : public IVerification { const std::shared_ptr& instance_; }; // class RecursiveVerification +// region Verification functions + +/// \brief Check a read-only list of base-class instances. +bool ItemsAreSome( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Pass a list of descendants to a base-class list parameter. +bool SomeItemsAreSome( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Preserve an optional descendant list passed to an optional parameter. +bool OptionalSomeItemsAreSome( + const common::optional< + std::vector< + std::shared_ptr + > + >& items +); + +/// \brief Check a possibly missing read-only list of base-class instances. +bool OptionalItemsAreSome( + const common::optional< + std::vector< + std::shared_ptr + > + >& items +); + +// endregion Verification functions + } // namespace verification /**@}*/ diff --git a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp index 40ac2d582..7a2e0161f 100644 --- a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp +++ b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp @@ -37,6 +37,68 @@ Error::Error( // endregion struct Error +// region Verification functions + +bool ItemsAreSome( + const std::vector< + std::shared_ptr + >& items +) { + return ( + common::All( + [&](const std::shared_ptr& item) -> bool { + return types::IsSomeItem(*item); + }, + items + ) + ); +} + +bool SomeItemsAreSome( + const std::vector< + std::shared_ptr + >& items +) { + return ( + ItemsAreSome( + items + ) + ); +} + +bool OptionalSomeItemsAreSome( + const common::optional< + std::vector< + std::shared_ptr + > + >& items +) { + return ( + OptionalItemsAreSome( + items + ) + ); +} + +bool OptionalItemsAreSome( + const common::optional< + std::vector< + std::shared_ptr + > + >& items +) { + return ( + ( + (!(items.has_value())) + || ItemsAreSome( + (*items) + ) + ) + ); +} + +// endregion Verification functions + namespace { /** diff --git a/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs b/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs index 014b3b7be..4b02182fc 100644 --- a/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs +++ b/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs @@ -31,6 +31,48 @@ namespace dummy /// public static class Verification { + /// + /// Check a read-only list of base-class instances. + /// + public static bool ItemsAreSome( + List items + ) + { + return items.All( + item => item is Our.ISomeItem); + } // public static bool ItemsAreSome + + /// + /// Pass a list of descendants to a base-class list parameter. + /// + public static bool SomeItemsAreSome( + List items + ) + { + return Verification.ItemsAreSome(items); + } // public static bool SomeItemsAreSome + + /// + /// Preserve an optional descendant list passed to an optional parameter. + /// + public static bool OptionalSomeItemsAreSome( + List? items + ) + { + return Verification.OptionalItemsAreSome(items); + } // public static bool OptionalSomeItemsAreSome + + /// + /// Check a possibly missing read-only list of base-class instances. + /// + public static bool OptionalItemsAreSome( + List? items + ) + { + return (items == null) + || Verification.ItemsAreSome(items); + } // public static bool OptionalItemsAreSome + /// /// Hash allowed enum values for efficient validation of enums. /// diff --git a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/internal/xmlcommon/xmlcommon.go index c0f4987e7..39650fc91 100644 --- a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/verification/verification.go b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/verification/verification.go index 980c4c87e..32fcc747f 100644 --- a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/verification/verification.go +++ b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/verification/verification.go @@ -584,7 +584,7 @@ func QualifierTypesAreUnique[Q ourtypes.IQualifier]( } func constructMatchesXMLSerializableString() *regexp.Regexp { - pattern := "^[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*$" + pattern := "^[\\t\\n\\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*$" return regexp.MustCompile( pattern, @@ -1853,7 +1853,7 @@ func MatchesXsNegativeInteger(text string) bool { } func constructMatchesXsString() *regexp.Regexp { - pattern := "^[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*$" + pattern := "^[\\t\\n\\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*$" return regexp.MustCompile( pattern, diff --git a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/common_test.go index fc537ecd3..aa50c83ee 100644 --- a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/aas-core-works/aas-core3.0-golang/ourtesting" + ourtypes "github.com/aas-core-works/aas-core3.0-golang/types" ourxmlization "github.com/aas-core-works/aas-core3.0-golang/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/concrete_classes_test.go index 1e3b7dea3..8928f110e 100644 --- a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestExtensionRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestAdministrativeInformationRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestQualifierRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestAssetAdministrationShellRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -572,7 +572,7 @@ func TestAssetInformationRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -698,7 +698,7 @@ func TestResourceRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -824,7 +824,7 @@ func TestSpecificAssetIDRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -950,7 +950,7 @@ func TestSubmodelRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1076,7 +1076,7 @@ func TestRelationshipElementRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1202,7 +1202,7 @@ func TestSubmodelElementListRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1328,7 +1328,7 @@ func TestSubmodelElementCollectionRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1454,7 +1454,7 @@ func TestPropertyRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1580,7 +1580,7 @@ func TestMultiLanguagePropertyRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1706,7 +1706,7 @@ func TestRangeRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1832,7 +1832,7 @@ func TestReferenceElementRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1958,7 +1958,7 @@ func TestBlobRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2084,7 +2084,7 @@ func TestFileRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2210,7 +2210,7 @@ func TestAnnotatedRelationshipElementRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2336,7 +2336,7 @@ func TestEntityRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2462,7 +2462,7 @@ func TestEventPayloadRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2588,7 +2588,7 @@ func TestBasicEventElementRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2714,7 +2714,7 @@ func TestOperationRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2840,7 +2840,7 @@ func TestOperationVariableRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -2966,7 +2966,7 @@ func TestCapabilityRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3092,7 +3092,7 @@ func TestConceptDescriptionRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3218,7 +3218,7 @@ func TestReferenceRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3344,7 +3344,7 @@ func TestKeyRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3470,7 +3470,7 @@ func TestLangStringNameTypeRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3596,7 +3596,7 @@ func TestLangStringTextTypeRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3722,7 +3722,7 @@ func TestEnvironmentRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3848,7 +3848,7 @@ func TestEmbeddedDataSpecificationRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -3974,7 +3974,7 @@ func TestLevelTypeRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -4100,7 +4100,7 @@ func TestValueReferencePairRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -4226,7 +4226,7 @@ func TestValueListRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -4352,7 +4352,7 @@ func TestLangStringPreferredNameTypeIEC61360RoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -4478,7 +4478,7 @@ func TestLangStringShortNameTypeIEC61360RoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -4604,7 +4604,7 @@ func TestLangStringDefinitionTypeIEC61360RoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -4730,7 +4730,7 @@ func TestDataSpecificationIEC61360RoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/concrete_classes_test.go index 6e7ebbea9..6d9aa5baa 100644 --- a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestChildARoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestChildBRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/arithmetic/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/arithmetic/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/arithmetic/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/arithmetic/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/concrete_classes_test.go index 101cde10d..97a637b6b 100644 --- a/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/arithmetic/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/assert_statements/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/assert_statements/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/assert_statements/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/assert_statements/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/concrete_classes_test.go index 71b775558..77f0032d2 100644 --- a/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/assert_statements/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestChildARoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestChildBRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/assignment_targets/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/assignment_targets/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/assignment_targets/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/assignment_targets/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/concrete_classes_test.go index 18b9efb52..a2bff97f7 100644 --- a/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/assignment_targets/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/concrete_classes_test.go index d81ac13ee..58d86244f 100644 --- a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestGroupedRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go index b3df3ae74..1923ace67 100644 --- a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/concrete_classes_test.go index a2e6120cf..2ab1b4cc1 100644 --- a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestQueryConditionRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/concrete_classes_test.go index f884d5a54..840acecd6 100644 --- a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestBranchRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestLeafRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestBlossomRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestPlainMarkerRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -572,7 +572,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -698,7 +698,7 @@ func TestContainerRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/empty_class/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/empty_class/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/empty_class/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/empty_class/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/concrete_classes_test.go index c24a62964..b3c395902 100644 --- a/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/empty_class/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/enum/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/enum/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/enum/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/enum/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/concrete_classes_test.go index 34e3ae28e..6c43baaff 100644 --- a/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/enum/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/float_only/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/float_only/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/float_only/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/float_only/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/concrete_classes_test.go index a8d8d7892..92cefa00a 100644 --- a/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/float_only/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/for_statements/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/for_statements/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/for_statements/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/for_statements/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/concrete_classes_test.go index 4dc2b178a..911e758f2 100644 --- a/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/for_statements/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/if_statements/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/if_statements/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/if_statements/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/if_statements/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/concrete_classes_test.go index ba618a7d6..1d04f08ca 100644 --- a/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/if_statements/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestChildARoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestChildBRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestContainerRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -572,7 +572,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/implementation_specific/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/implementation_specific/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/implementation_specific/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/implementation_specific/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/concrete_classes_test.go index c51c6cee9..a985e7ac8 100644 --- a/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/implementation_specific/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestBoxRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestBagRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestContainerRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/concrete_classes_test.go index 342ae9cf5..c0f34ee3b 100644 --- a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/concrete_classes_test.go index 37679af6c..95e2192ca 100644 --- a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/is_instance/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/is_instance/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/is_instance/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/is_instance/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/concrete_classes_test.go index 2b03b8be8..ea7dc48d6 100644 --- a/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/is_instance/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestLeafRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestOrderedContainerRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestUnorderedContainerRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestGlobalAttributeRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -572,7 +572,7 @@ func TestLocalAttributeRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -698,7 +698,7 @@ func TestAttributeOperandRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -824,7 +824,7 @@ func TestStringLiteralRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -950,7 +950,7 @@ func TestNumberLiteralRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1076,7 +1076,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/json_types/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/json_types/expected_output/internal/xmlcommon/xmlcommon.go index af2fbf335..bd2aad58d 100644 --- a/dev/test_data/main/golang/expected/json_types/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/json_types/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/concrete_classes_test.go index 2114c06bd..224816e07 100644 --- a/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/json_types/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/internal/xmlcommon/xmlcommon.go index af2fbf335..bd2aad58d 100644 --- a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/concrete_classes_test.go index a2f2029b3..70d88f768 100644 --- a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/internal/xmlcommon/xmlcommon.go index af2fbf335..bd2aad58d 100644 --- a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/concrete_classes_test.go index a2571f811..97badc577 100644 --- a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/list_of_classes/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/list_of_classes/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/list_of_classes/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/list_of_classes/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go b/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go index 26a54b221..90f60efc8 100644 --- a/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go +++ b/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go @@ -12,6 +12,7 @@ package verification import ( "fmt" + ourcommon "github.com/dummy-works/dummy/common" ourreporting "github.com/dummy-works/dummy/reporting" ourtypes "github.com/dummy-works/dummy/types" ) @@ -44,6 +45,62 @@ func (ve *VerificationError) PathString() string { return ourreporting.ToGolangPath(ve.Path) } +// Check a read-only list of base-class instances. +func ItemsAreSome( + items []ourtypes.IAbstractItem, +) bool { + return ourcommon.All( + func(item ourtypes.IAbstractItem) bool { + return ourtypes.IsSomeItem(item) + }, + items, + ) +} + +// Pass a list of descendants to a base-class list parameter. +func SomeItemsAreSome( + items []ourtypes.ISomeItem, +) bool { + return ItemsAreSome( + func(items []ourtypes.ISomeItem) []ourtypes.IAbstractItem { + if items == nil { + return nil + } + result := make([]ourtypes.IAbstractItem, len(items)) + for i, item := range items { + result[i] = item + } + return result + }(items), + ) +} + +// Preserve an optional descendant list passed to an optional parameter. +func OptionalSomeItemsAreSome( + items []ourtypes.ISomeItem, +) bool { + return OptionalItemsAreSome( + func(items []ourtypes.ISomeItem) []ourtypes.IAbstractItem { + if items == nil { + return nil + } + result := make([]ourtypes.IAbstractItem, len(items)) + for i, item := range items { + result[i] = item + } + return result + }(items), + ) +} + +// Check a possibly missing read-only list of base-class instances. +func OptionalItemsAreSome( + items []ourtypes.IAbstractItem, +) bool { + return (items == nil) || + ItemsAreSome(items) +} + // Verify `that` instance of [ourtypes.ISomeItem]. // // You have to supply the callback `onError` to iterate over the errors. diff --git a/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/concrete_classes_test.go index 797e4f988..dbd32a783 100644 --- a/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/list_of_classes/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomeItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestAnotherItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestSimpleRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go index aa958a005..e0287ac19 100644 --- a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/list_of_enums/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/list_of_enums/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/list_of_enums/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/list_of_enums/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/concrete_classes_test.go index 528ecc4de..6fbd8bb00 100644 --- a/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/list_of_enums/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/concrete_classes_test.go index 186fd0ba2..9685bdf99 100644 --- a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/method_references/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/method_references/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/method_references/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/method_references/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/concrete_classes_test.go index acb50f616..ef290fd61 100644 --- a/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/method_references/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestBoxRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/concrete_classes_test.go index bb4b90cdd..4b3e82bfb 100644 --- a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestLangStringRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestLangStringSetRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestIecContentRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestOtherContentRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -572,7 +572,7 @@ func TestSpecificationRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -698,7 +698,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/nested_collections/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/nested_collections/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/nested_collections/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/nested_collections/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/concrete_classes_test.go index 203dd7301..b06c770d2 100644 --- a/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/nested_collections/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomeItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestAnotherItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/optional_arguments/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/optional_arguments/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/optional_arguments/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/optional_arguments/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/concrete_classes_test.go index b5d7bc5e9..c87eabe5d 100644 --- a/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/optional_arguments/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/primitive_types/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/primitive_types/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/primitive_types/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/primitive_types/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/concrete_classes_test.go index e40e4c204..7278cb63d 100644 --- a/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/primitive_types/expected_output/xmlization/test/concrete_classes_test.go @@ -69,7 +69,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/concrete_classes_test.go index 05552338d..599c7624a 100644 --- a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/sets/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/sets/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/sets/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/sets/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/concrete_classes_test.go index c78bd3bb7..3cb41d7f7 100644 --- a/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/sets/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestCollectionRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/concrete_classes_test.go index 66b7f602d..ab7e0adf9 100644 --- a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/switch_statements/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/switch_statements/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/switch_statements/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/switch_statements/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/concrete_classes_test.go index 5fc54fc5a..d37f9adfb 100644 --- a/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/switch_statements/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/concrete_classes_test.go index 6250f002d..0d4f0603f 100644 --- a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestFirstRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestSecondRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/tuples/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/tuples/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/tuples/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/tuples/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/concrete_classes_test.go index 203dd7301..b06c770d2 100644 --- a/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/tuples/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestSomeItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestAnotherItemRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/unions/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/unions/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/unions/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/unions/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/concrete_classes_test.go index ffaf16fea..ea3997045 100644 --- a/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/unions/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestStructuralFirstRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -194,7 +194,7 @@ func TestStructuralSecondRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -320,7 +320,7 @@ func TestMixedAbstractDescendantOneRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -446,7 +446,7 @@ func TestMixedAbstractDescendantTwoRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -572,7 +572,7 @@ func TestMixedConcreteWithDescendantsRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -698,7 +698,7 @@ func TestMixedConcreteWithDescendantsChildRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -824,7 +824,7 @@ func TestMixedConcreteLeafRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -950,7 +950,7 @@ func TestModelTypedFirstRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1076,7 +1076,7 @@ func TestModelTypedSecondRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) @@ -1202,7 +1202,7 @@ func TestSomethingRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/golang/expected/visibility/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/visibility/expected_output/internal/xmlcommon/xmlcommon.go index 436dbd116..e7bcae2b2 100644 --- a/dev/test_data/main/golang/expected/visibility/expected_output/internal/xmlcommon/xmlcommon.go +++ b/dev/test_data/main/golang/expected/visibility/expected_output/internal/xmlcommon/xmlcommon.go @@ -837,6 +837,11 @@ func readElementDispatchedInNamespace[T any]( return } + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + err = checkEndElementInNamespace(current, local, ns) if err != nil { return diff --git a/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/common_test.go index f092c4c7d..edefb227a 100644 --- a/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/common_test.go +++ b/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/common_test.go @@ -4,13 +4,14 @@ package xmlization_test // Do NOT edit or append. import ( + "encoding/xml" "fmt" "os" "path/filepath" - "regexp" "strings" "testing" ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" ourxmlization "github.com/dummy-works/dummy/xmlization" ) @@ -53,148 +54,25 @@ func assertNoSerializationError( return } -// NOTE (mristin): -// Currently, Go does not support self-closing tags, -// see: https://github.com/golang/go/issues/21399. -// We apply the following hack to make the tags self-closing even if they are not. -// This is unsafe in general, but works OK for the limited set of test data that we are -// here dealing with. -// -// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 - -// NOTE (mristin): -// The opening tag, tag name plus an optional attribute list (*e.g.*, -// ``xmlns="..."`` on a root element), is captured in the first (and only) -// submatch group so that we do not lose any attributes when we force -// the tag to be self-closing below. The attribute list must not end with -// a slash, as we would otherwise match an already self-closing tag followed -// by the closing tag of its parent, *e.g.*, ````. -var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) - -func forceSelfClosingTags(text string) string { - b := []byte(text) - emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) - - if len(emptyTagIdxs) == 0 { - return text - } - - // NOTE (mristin): - // The matches are walked in order, and the pieces of `b` between them are - // copied over as they are. Mind that `nb` starts out empty and that `b` is - // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which - // writes into `b`'s own backing array, so a document with more than one - // empty tag came out garbled -- every turn of the loop then indexed into - // a `b` which the previous turn had already overwritten. - var nb []byte - previous := 0 - - for _, idx := range emptyTagIdxs { - // Copy everything since the previous match, then the tag itself -- - // its name and its attributes -- as a self-closing one. - nb = append(nb, b[previous:idx[0]]...) - nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) - - previous = idx[1] - } - - // Finally, copy everything after the last match. - nb = append(nb, b[previous:]...) - - return string(nb) -} - -var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) - -// Remove all whitespace (including newlines, tabs, and spaces) between XML tags -// without parsing the XML. -func removeWhitespaceBetweenTags(text string) string { - return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") -} - -var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) -var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) -var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) - -// Remove any whitespace before `>` and `/>` or after `<` -func removeWhitespaceWithinTags(text string) string { - result := text - result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") - result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") - result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") - return result -} - -// Assert that the serialization `other`, as XML document, equals the original -// -// XML document `that` read from the `source`. +// Assert that serializing and de-serializing preserves the original instance. func assertSerializationEqualsDeserialization( t *testing.T, - that string, + that ourtypes.IClass, other string, source string, ) (ok bool) { - ok = true - - // Remove carriers to avoid problems between Windows, Posix and MacOS - canonicalThat := strings.ReplaceAll(that, "\r", "") - canonicalOther := strings.ReplaceAll(other, "\r", "") - - canonicalThat = strings.TrimSpace(canonicalThat) - canonicalOther = strings.TrimSpace(canonicalOther) - - canonicalThat = forceSelfClosingTags(canonicalThat) - canonicalOther = forceSelfClosingTags(canonicalOther) - - // NOTE (mristin): - // The following hack is SUPER ugly and unsafe! However, it works. Given Go's - // limited support for XML, we gave up on a safer approach :(. We tested - // the following approaches before applying this hack: - // * A round-trip over `encoding/xml`. Failed due to - // https://github.com/golang/go/issues/13400. - // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the - // element content still has not been de-escaped or consistently escaped in - // a round trip. - - canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") - canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") - - canonicalThat = removeWhitespaceBetweenTags(canonicalThat) - canonicalOther = removeWhitespaceBetweenTags(canonicalOther) - - canonicalThat = removeWhitespaceWithinTags(canonicalThat) - canonicalOther = removeWhitespaceWithinTags(canonicalOther) - - thatLines := strings.Split(canonicalThat, "\n") - otherLines := strings.Split(canonicalOther, "\n") - - if canonicalThat != canonicalOther { - b := new(strings.Builder) - minLines := len(thatLines) - if minLines > len(otherLines) { - minLines = len(otherLines) - } - for i := 0; i < minLines; i++ { - if thatLines[i] == otherLines[i] { - b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) - } else { - b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) - b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) - break - } - } + decoder := xml.NewDecoder(strings.NewReader(other)) + roundTripped, err := ourxmlization.Unmarshal(decoder) + if !assertNoDeserializationError(t, err, source) { + return false + } - ok = false - t.Fatalf( - "The canonicalized XML serialization of the de-serialized instance "+ - "from %s does not equal the canonicalized original XML document:\n"+ - "%s", - source, b.String(), - ) - return + if !ourtesting.DeepEqual(that, roundTripped) { + t.Fatalf("XML round-trip changed the instance from %s", source) + return false } - return + return true } // Assert that there is a de-serialization error. diff --git a/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/concrete_classes_test.go index c300e0870..2a7ef9e3c 100644 --- a/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/concrete_classes_test.go +++ b/dev/test_data/main/golang/expected/visibility/expected_output/xmlization/test/concrete_classes_test.go @@ -68,7 +68,7 @@ func TestBoxRoundTripOK(t *testing.T) { ok = assertSerializationEqualsDeserialization( t, - text, + deserialized, roundTrip, pth, ) diff --git a/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java b/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java index 7beb43f66..5e27769f0 100644 --- a/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java +++ b/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java @@ -28,8 +28,43 @@ import dummy.types.model.*; import dummy.visitation.AbstractTransformer; import java.util.List; +import java.util.Optional; public class Verification { + /** + * Check a read-only list of base-class instances. + */ + public static Boolean itemsAreSome( + List items) { + return items.stream().allMatch( + item -> item instanceof ISomeItem); + } + + /** + * Pass a list of descendants to a base-class list parameter. + */ + public static Boolean someItemsAreSome( + List items) { + return itemsAreSome(items); + } + + /** + * Preserve an optional descendant list passed to an optional parameter. + */ + public static Boolean optionalSomeItemsAreSome( + Optional> items) { + return optionalItemsAreSome(items); + } + + /** + * Check a possibly missing read-only list of base-class instances. + */ + public static Boolean optionalItemsAreSome( + Optional> items) { + return (!items.isPresent()) + || itemsAreSome(items.orElse(null)); + } + /** * Hash allowed enum values for efficient validation of enums. */ diff --git a/dev/test_data/main/python/expected/list_of_classes/expected_output/dummy/verification.py b/dev/test_data/main/python/expected/list_of_classes/expected_output/dummy/verification.py index a68d63f87..5b328630e 100644 --- a/dev/test_data/main/python/expected/list_of_classes/expected_output/dummy/verification.py +++ b/dev/test_data/main/python/expected/list_of_classes/expected_output/dummy/verification.py @@ -62,6 +62,50 @@ Error = our_reporting.Error +def items_are_some( + items: Sequence[our_types.AbstractItem] +) -> bool: + """Check a read-only list of base-class instances.""" + # pylint: disable=all + return ( + all( + isinstance(item, our_types.SomeItem) + for item in items + )) + + +def some_items_are_some( + items: Sequence[our_types.SomeItem] +) -> bool: + """Pass a list of descendants to a base-class list parameter.""" + # pylint: disable=all + return items_are_some(items) + + +def optional_some_items_are_some( + items: Optional[Sequence[our_types.SomeItem]] +) -> bool: + """ + Preserve an optional descendant list passed to an optional parameter. + """ + # pylint: disable=all + return optional_items_are_some(items) + + +def optional_items_are_some( + items: Optional[Sequence[our_types.AbstractItem]] +) -> bool: + """ + Check a possibly missing read-only list of base-class instances. + """ + # pylint: disable=all + return ( + ( + (items is None) + or items_are_some(items) + )) + + class _Transformer( our_types.AbstractTransformer[ Iterator[Error] diff --git a/dev/test_data/main/typescript/expected/list_of_classes/expected_output/src/verification.ts b/dev/test_data/main/typescript/expected/list_of_classes/expected_output/src/verification.ts index 2538f6bcf..20a87e801 100644 --- a/dev/test_data/main/typescript/expected/list_of_classes/expected_output/src/verification.ts +++ b/dev/test_data/main/typescript/expected/list_of_classes/expected_output/src/verification.ts @@ -189,6 +189,51 @@ export class VerificationError { } } +/** + * Check a read-only list of base-class instances. + */ +export function itemsAreSome( + items: Array +): boolean { + return OurCommon.every( + OurCommon.map( + items, + item => + OurTypes.isSomeItem(item) + ) + ); +} + +/** + * Pass a list of descendants to a base-class list parameter. + */ +export function someItemsAreSome( + items: Array +): boolean { + return itemsAreSome(items); +} + +/** + * Preserve an optional descendant list passed to an optional parameter. + */ +export function optionalSomeItemsAreSome( + items: Array | null +): boolean { + return optionalItemsAreSome(items); +} + +/** + * Check a possibly missing read-only list of base-class instances. + */ +export function optionalItemsAreSome( + items: Array | null +): boolean { + return ( + (items === null) + || itemsAreSome(items) + ); +} + /** * Verify the items of `that` recursively. */ diff --git a/dev/tests/golang/test_verification.py b/dev/tests/golang/test_verification.py new file mode 100644 index 000000000..4091e4c5b --- /dev/null +++ b/dev/tests/golang/test_verification.py @@ -0,0 +1,35 @@ +# pylint: disable=missing-docstring + +import re +import unittest + +from aas_core_codegen.golang.lib import _generate_verification as golang_verification +from aas_core_codegen.parse import retree as parse_retree + + +class TestRegexRenderer(unittest.TestCase): + def test_encoded_metacharacters(self) -> None: + for pattern in [ + r"^\x5Btext\x5D$", + r"^[\x5B\x5D\x5E\x2D\x5C]+$", + r"^[\x20-\x5B\x5D-\uD7FF]+$", + ]: + with self.subTest(pattern=pattern): + parsed, error = parse_retree.parse([pattern]) + self.assertIsNone(error) + assert parsed is not None + rendered = parse_retree.render( + parsed, golang_verification.RegexRenderer() + ) + self.assertEqual(1, len(rendered)) + assert isinstance(rendered[0], str) + for text in ["[text]", "[", "]", "^", "-", "\\", "text", "\uD7FF"]: + with self.subTest(text=text): + self.assertEqual( + re.fullmatch(pattern, text) is not None, + re.fullmatch(rendered[0], text) is not None, + ) + + +if __name__ == "__main__": + unittest.main()