From df728755e6e8085dbab2f0468d0fb91d21d0ca51 Mon Sep 17 00:00:00 2001 From: Michael Adler Date: Mon, 15 Dec 2025 11:51:16 +0100 Subject: [PATCH] feat: add native parsing for xsd:float Implement native parsing support for xsd:float. Refactor the parsing logic to use a switch-case structure for better readability and maintainability, replacing the previous if-else chain. Integer and double cases are now handled separately, allowing the use of the most suitable standard library parsing methods. This change also eliminates the use of fmt.Sprintf for float-to-integer checks, improving performance. Signed-off-by: Michael Adler --- ld/node.go | 47 ++++++++++++++++------------- ld/node_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 ld/node_test.go diff --git a/ld/node.go b/ld/node.go index 51fbc94..5eb356e 100644 --- a/ld/node.go +++ b/ld/node.go @@ -189,34 +189,39 @@ func RdfToObject(n Node, useNativeTypes bool) (map[string]interface{}, error) { value := literal.Value if useNativeTypes { // use native datatypes for certain xsd types - if datatype == XSDString { - // don't add xsd:string - } else if datatype == XSDBoolean { - if value == "true" { + switch datatype { + case XSDString: + // prevent default case from matching, i.e. prevent adding @type for xsd:string + case XSDBoolean: + switch value { + case "true": rval["@value"] = true - } else if value == "false" { + case "false": rval["@value"] = false - } else { - // Else do not replace the value, and add the - // boolean type in + default: + // do not replace the value but add the boolean type rval["@type"] = datatype } - } else if (datatype == XSDInteger && patternInteger.MatchString(value)) /* http://www.w3.org/TR/xmlschema11-2/#integer */ || - (datatype == XSDDouble && patternDouble.MatchString(value)) /* http://www.w3.org/TR/xmlschema11-2/#nt-doubleRep */ { - d, _ := strconv.ParseFloat(value, 64) - if !math.IsNaN(d) && !math.IsInf(d, 0) { - if datatype == XSDInteger { - i := int64(d) - if fmt.Sprintf("%d", i) == value { - rval["@value"] = i - } - } else if datatype == XSDDouble { - rval["@value"] = d - } else { + case XSDInteger: // http://www.w3.org/TR/xmlschema11-2/#integer + if patternInteger.MatchString(value) { + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { return nil, NewJsonLdError(ParseError, nil) } + rval["@value"] = i } - } else { + case XSDDouble, XSDFloat: // http://www.w3.org/TR/xmlschema11-2/#nt-doubleRep + if patternDouble.MatchString(value) { + d, err := strconv.ParseFloat(value, 64) + if err != nil { + return nil, NewJsonLdError(ParseError, nil) + } + // ParseFloat successfully parses strings like "NaN", "Inf", and "-Inf" without returning an error + if !math.IsNaN(d) && !math.IsInf(d, 0) { + rval["@value"] = d + } + } + default: // do not add xsd:string type rval["@type"] = datatype } diff --git a/ld/node_test.go b/ld/node_test.go new file mode 100644 index 0000000..c35b9ca --- /dev/null +++ b/ld/node_test.go @@ -0,0 +1,78 @@ +// Copyright 2025 Siemens AG +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ld + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRdfToObject_NativeTypes(t *testing.T) { + testCases := []struct { + name string + literal Literal + expected map[string]any + }{ + { + name: "Boolean true", + literal: Literal{Value: "true", Datatype: XSDBoolean}, + expected: map[string]any{"@value": true}, + }, + { + name: "Boolean false", + literal: Literal{Value: "false", Datatype: XSDBoolean}, + expected: map[string]any{"@value": false}, + }, + { + name: "Boolean True", + literal: Literal{Value: "True", Datatype: XSDBoolean}, + expected: map[string]any{"@value": "True", "@type": XSDBoolean}, + }, + { + name: "Float", + literal: Literal{Value: "3.141", Datatype: XSDFloat}, + expected: map[string]any{"@value": float64(3.141)}, + }, + { + name: "Double", + literal: Literal{Value: "2.71828", Datatype: XSDDouble}, + expected: map[string]any{"@value": float64(2.71828)}, + }, + { + name: "Integer", + literal: Literal{Value: "42", Datatype: XSDInteger}, + expected: map[string]any{"@value": int64(42)}, + }, + { + name: "String without @type", + literal: Literal{Value: "hello world", Datatype: XSDString}, + expected: map[string]any{"@value": "hello world"}, + }, + { + name: "Decimal", + literal: Literal{Value: "3.141", Datatype: XSDDecimal}, + expected: map[string]any{"@value": "3.141", "@type": XSDDecimal}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + converted, err := RdfToObject(tc.literal, true) + require.NoError(t, err) + assert.Equal(t, tc.expected, converted) + }) + } +}