150 lines
3.5 KiB
Go
150 lines
3.5 KiB
Go
package phpx
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/tkw1536/pkglib/collection"
|
|
)
|
|
|
|
// Marshal marshals data as a PHP expression, so that it can be safely used inside a php expession.
|
|
//
|
|
// Typically data is marshaled using [json.Marshal] and decoded in PHP using 'json_decode'.
|
|
// Special cases may exist for specific datatypes.
|
|
func Marshal(data any) (string, error) {
|
|
switch d := data.(type) {
|
|
case string:
|
|
return MarshalString(d), nil
|
|
}
|
|
|
|
bytes, err := json.Marshal(data)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return "json_decode(" + MarshalString(string(bytes)) + ")", nil
|
|
}
|
|
|
|
// MarshalJSON marshals a json-value safely as an expression to be used as a php string.
|
|
//
|
|
// A json value is one returned by calling [json.Unmarshal] on a value of type any.
|
|
// These are then marshaled by the appropriate function:
|
|
//
|
|
// - a nil is turned into [PHPNil]
|
|
// - a bool is passed to [MarshalBool]
|
|
// - a float64 is passed to [MarshalFloat]
|
|
// - a string is passed to [MarshalString]
|
|
// - an []any is passed to [MarshalSlice]
|
|
// - an map[string]any is passed to [MarshalMap]
|
|
//
|
|
// All marshaling attempts to minify the length of the returned string, meaning compact encodings
|
|
// are prefered over length ones.
|
|
//
|
|
// If a value is none of these types, an empty string is returned.
|
|
// No valid value ever returns the empty string
|
|
func MarshalJSON(v any) string {
|
|
switch v := v.(type) {
|
|
case nil:
|
|
return PHPNil
|
|
case bool:
|
|
return MarshalBool(v)
|
|
case float64:
|
|
return MarshalFloat(v)
|
|
case string:
|
|
return MarshalString(v)
|
|
case []any:
|
|
return MarshalSlice(v)
|
|
case map[string]any:
|
|
return MarshalMap(v)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
const (
|
|
// PHPNil represents the equivalent of a nil value in php
|
|
PHPNil = "null"
|
|
|
|
phpTrue = "!0"
|
|
phpFalse = "!1"
|
|
|
|
phpNaN = "NAN"
|
|
phpPositiveInfinity = "INF"
|
|
phpNegativeInfinity = "-INF"
|
|
)
|
|
|
|
// MarshalBool marshals b as a boolean to be used in php code.
|
|
// This corresponds to the strings "true" or "false".
|
|
func MarshalBool(b bool) string {
|
|
if b {
|
|
return phpTrue
|
|
}
|
|
return phpFalse
|
|
}
|
|
|
|
// MarshalFloat marshals a floating point number or integer
|
|
func MarshalFloat(f float64) string {
|
|
// if we actually have an integer, return it!
|
|
if i := int64(f); f == float64(i) {
|
|
return MarshalInt(i)
|
|
}
|
|
|
|
// special cases
|
|
if math.IsNaN(f) {
|
|
return phpNaN
|
|
}
|
|
if math.IsInf(f, 1) {
|
|
return phpPositiveInfinity
|
|
}
|
|
if math.IsInf(f, -1) {
|
|
return phpNegativeInfinity
|
|
}
|
|
|
|
// all other cases
|
|
return strconv.FormatFloat(f, 'E', -1, 64)
|
|
}
|
|
|
|
// MarshalInt marshals an integer as a string to be used inside a php literal
|
|
func MarshalInt(i int64) string {
|
|
return strconv.FormatInt(i, 10)
|
|
}
|
|
|
|
var stringReplacer = strings.NewReplacer("'", "\\'", "\\", "\\\\")
|
|
|
|
// MarshalString marshals s as a php string that can be used safely as a PHP expression.
|
|
func MarshalString(s string) string {
|
|
// See [https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.single]
|
|
// we just escape
|
|
return "'" + stringReplacer.Replace(s) + "'"
|
|
}
|
|
|
|
func MarshalSlice(slice []any) string {
|
|
var builder strings.Builder
|
|
|
|
builder.WriteRune('[')
|
|
{
|
|
for _, v := range slice {
|
|
builder.WriteString(MarshalJSON(v))
|
|
builder.WriteRune(',')
|
|
}
|
|
}
|
|
builder.WriteRune(']')
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
func MarshalMap(m map[string]any) string {
|
|
var builder strings.Builder
|
|
|
|
builder.WriteString("array(")
|
|
collection.IterateSorted(m, func(k string, v any) {
|
|
builder.WriteString(MarshalString(k))
|
|
builder.WriteString("=>")
|
|
builder.WriteString(MarshalJSON(v))
|
|
builder.WriteString(",")
|
|
})
|
|
builder.WriteString(")")
|
|
|
|
return builder.String()
|
|
}
|