| // Copyright (C) MongoDB, Inc. 2017-present. |
| // |
| // 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 |
| |
| package bsonrw |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "fmt" |
| "github.com/mongodb/mongo-go-driver/bson/primitive" |
| "io" |
| "math" |
| "sort" |
| "strconv" |
| "strings" |
| "sync" |
| "time" |
| "unicode/utf8" |
| ) |
| |
| var ejvwPool = sync.Pool{ |
| New: func() interface{} { |
| return new(extJSONValueWriter) |
| }, |
| } |
| |
| // ExtJSONValueWriterPool is a pool for ExtJSON ValueWriters. |
| type ExtJSONValueWriterPool struct { |
| pool sync.Pool |
| } |
| |
| // NewExtJSONValueWriterPool creates a new pool for ValueWriter instances that write to ExtJSON. |
| func NewExtJSONValueWriterPool() *ExtJSONValueWriterPool { |
| return &ExtJSONValueWriterPool{ |
| pool: sync.Pool{ |
| New: func() interface{} { |
| return new(extJSONValueWriter) |
| }, |
| }, |
| } |
| } |
| |
| // Get retrieves a ExtJSON ValueWriter from the pool and resets it to use w as the destination. |
| func (bvwp *ExtJSONValueWriterPool) Get(w io.Writer, canonical, escapeHTML bool) ValueWriter { |
| vw := bvwp.pool.Get().(*extJSONValueWriter) |
| if writer, ok := w.(*SliceWriter); ok { |
| vw.reset(*writer, canonical, escapeHTML) |
| vw.w = writer |
| return vw |
| } |
| vw.buf = vw.buf[:0] |
| vw.w = w |
| return vw |
| } |
| |
| // Put inserts a ValueWriter into the pool. If the ValueWriter is not a ExtJSON ValueWriter, nothing |
| // happens and ok will be false. |
| func (bvwp *ExtJSONValueWriterPool) Put(vw ValueWriter) (ok bool) { |
| bvw, ok := vw.(*extJSONValueWriter) |
| if !ok { |
| return false |
| } |
| |
| if _, ok := bvw.w.(*SliceWriter); ok { |
| bvw.buf = nil |
| } |
| bvw.w = nil |
| |
| bvwp.pool.Put(bvw) |
| return true |
| } |
| |
| type ejvwState struct { |
| mode mode |
| } |
| |
| type extJSONValueWriter struct { |
| w io.Writer |
| buf []byte |
| |
| stack []ejvwState |
| frame int64 |
| canonical bool |
| escapeHTML bool |
| } |
| |
| // NewExtJSONValueWriter creates a ValueWriter that writes Extended JSON to w. |
| func NewExtJSONValueWriter(w io.Writer, canonical, escapeHTML bool) (ValueWriter, error) { |
| if w == nil { |
| return nil, errNilWriter |
| } |
| |
| return newExtJSONWriter(w, canonical, escapeHTML), nil |
| } |
| |
| func newExtJSONWriter(w io.Writer, canonical, escapeHTML bool) *extJSONValueWriter { |
| stack := make([]ejvwState, 1, 5) |
| stack[0] = ejvwState{mode: mTopLevel} |
| |
| return &extJSONValueWriter{ |
| w: w, |
| buf: []byte{}, |
| stack: stack, |
| canonical: canonical, |
| escapeHTML: escapeHTML, |
| } |
| } |
| |
| func newExtJSONWriterFromSlice(buf []byte, canonical, escapeHTML bool) *extJSONValueWriter { |
| stack := make([]ejvwState, 1, 5) |
| stack[0] = ejvwState{mode: mTopLevel} |
| |
| return &extJSONValueWriter{ |
| buf: buf, |
| stack: stack, |
| canonical: canonical, |
| escapeHTML: escapeHTML, |
| } |
| } |
| |
| func (ejvw *extJSONValueWriter) reset(buf []byte, canonical, escapeHTML bool) { |
| if ejvw.stack == nil { |
| ejvw.stack = make([]ejvwState, 1, 5) |
| } |
| |
| ejvw.stack = ejvw.stack[:1] |
| ejvw.stack[0] = ejvwState{mode: mTopLevel} |
| ejvw.canonical = canonical |
| ejvw.escapeHTML = escapeHTML |
| ejvw.frame = 0 |
| ejvw.buf = buf |
| ejvw.w = nil |
| } |
| |
| func (ejvw *extJSONValueWriter) advanceFrame() { |
| if ejvw.frame+1 >= int64(len(ejvw.stack)) { // We need to grow the stack |
| length := len(ejvw.stack) |
| if length+1 >= cap(ejvw.stack) { |
| // double it |
| buf := make([]ejvwState, 2*cap(ejvw.stack)+1) |
| copy(buf, ejvw.stack) |
| ejvw.stack = buf |
| } |
| ejvw.stack = ejvw.stack[:length+1] |
| } |
| ejvw.frame++ |
| } |
| |
| func (ejvw *extJSONValueWriter) push(m mode) { |
| ejvw.advanceFrame() |
| |
| ejvw.stack[ejvw.frame].mode = m |
| } |
| |
| func (ejvw *extJSONValueWriter) pop() { |
| switch ejvw.stack[ejvw.frame].mode { |
| case mElement, mValue: |
| ejvw.frame-- |
| case mDocument, mArray, mCodeWithScope: |
| ejvw.frame -= 2 // we pop twice to jump over the mElement: mDocument -> mElement -> mDocument/mTopLevel/etc... |
| } |
| } |
| |
| func (ejvw *extJSONValueWriter) invalidTransitionErr(destination mode, name string, modes []mode) error { |
| te := TransitionError{ |
| name: name, |
| current: ejvw.stack[ejvw.frame].mode, |
| destination: destination, |
| modes: modes, |
| action: "write", |
| } |
| if ejvw.frame != 0 { |
| te.parent = ejvw.stack[ejvw.frame-1].mode |
| } |
| return te |
| } |
| |
| func (ejvw *extJSONValueWriter) ensureElementValue(destination mode, callerName string, addmodes ...mode) error { |
| switch ejvw.stack[ejvw.frame].mode { |
| case mElement, mValue: |
| default: |
| modes := []mode{mElement, mValue} |
| if addmodes != nil { |
| modes = append(modes, addmodes...) |
| } |
| return ejvw.invalidTransitionErr(destination, callerName, modes) |
| } |
| |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) writeExtendedSingleValue(key string, value string, quotes bool) { |
| var s string |
| if quotes { |
| s = fmt.Sprintf(`{"$%s":"%s"}`, key, value) |
| } else { |
| s = fmt.Sprintf(`{"$%s":%s}`, key, value) |
| } |
| |
| ejvw.buf = append(ejvw.buf, []byte(s)...) |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteArray() (ArrayWriter, error) { |
| if err := ejvw.ensureElementValue(mArray, "WriteArray"); err != nil { |
| return nil, err |
| } |
| |
| ejvw.buf = append(ejvw.buf, '[') |
| |
| ejvw.push(mArray) |
| return ejvw, nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteBinary(b []byte) error { |
| return ejvw.WriteBinaryWithSubtype(b, 0x00) |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteBinaryWithSubtype(b []byte, btype byte) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteBinaryWithSubtype"); err != nil { |
| return err |
| } |
| |
| var buf bytes.Buffer |
| buf.WriteString(`{"$binary":{"base64":"`) |
| buf.WriteString(base64.StdEncoding.EncodeToString(b)) |
| buf.WriteString(fmt.Sprintf(`","subType":"%02x"}},`, btype)) |
| |
| ejvw.buf = append(ejvw.buf, buf.Bytes()...) |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteBoolean(b bool) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteBoolean"); err != nil { |
| return err |
| } |
| |
| ejvw.buf = append(ejvw.buf, []byte(strconv.FormatBool(b))...) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteCodeWithScope(code string) (DocumentWriter, error) { |
| if err := ejvw.ensureElementValue(mCodeWithScope, "WriteCodeWithScope"); err != nil { |
| return nil, err |
| } |
| |
| var buf bytes.Buffer |
| buf.WriteString(`{"$code":`) |
| writeStringWithEscapes(code, &buf, ejvw.escapeHTML) |
| buf.WriteString(`,"$scope":{`) |
| |
| ejvw.buf = append(ejvw.buf, buf.Bytes()...) |
| |
| ejvw.push(mCodeWithScope) |
| return ejvw, nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteDBPointer(ns string, oid primitive.ObjectID) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteDBPointer"); err != nil { |
| return err |
| } |
| |
| var buf bytes.Buffer |
| buf.WriteString(`{"$dbPointer":{"$ref":"`) |
| buf.WriteString(ns) |
| buf.WriteString(`","$id":{"$oid":"`) |
| buf.WriteString(oid.Hex()) |
| buf.WriteString(`"}}},`) |
| |
| ejvw.buf = append(ejvw.buf, buf.Bytes()...) |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteDateTime(dt int64) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteDateTime"); err != nil { |
| return err |
| } |
| |
| t := time.Unix(dt/1e3, dt%1e3*1e6).UTC() |
| |
| if ejvw.canonical || t.Year() < 1970 || t.Year() > 9999 { |
| s := fmt.Sprintf(`{"$numberLong":"%d"}`, dt) |
| ejvw.writeExtendedSingleValue("date", s, false) |
| } else { |
| ejvw.writeExtendedSingleValue("date", t.Format(rfc3339Milli), true) |
| } |
| |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteDecimal128(d primitive.Decimal128) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteDecimal128"); err != nil { |
| return err |
| } |
| |
| ejvw.writeExtendedSingleValue("numberDecimal", d.String(), true) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteDocument() (DocumentWriter, error) { |
| if ejvw.stack[ejvw.frame].mode == mTopLevel { |
| ejvw.buf = append(ejvw.buf, '{') |
| return ejvw, nil |
| } |
| |
| if err := ejvw.ensureElementValue(mDocument, "WriteDocument", mTopLevel); err != nil { |
| return nil, err |
| } |
| |
| ejvw.buf = append(ejvw.buf, '{') |
| ejvw.push(mDocument) |
| return ejvw, nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteDouble(f float64) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteDouble"); err != nil { |
| return err |
| } |
| |
| s := formatDouble(f) |
| |
| if ejvw.canonical { |
| ejvw.writeExtendedSingleValue("numberDouble", s, true) |
| } else { |
| switch s { |
| case "Infinity": |
| fallthrough |
| case "-Infinity": |
| fallthrough |
| case "NaN": |
| s = fmt.Sprintf(`{"$numberDouble":"%s"}`, s) |
| } |
| ejvw.buf = append(ejvw.buf, []byte(s)...) |
| } |
| |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteInt32(i int32) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteInt32"); err != nil { |
| return err |
| } |
| |
| s := strconv.FormatInt(int64(i), 10) |
| |
| if ejvw.canonical { |
| ejvw.writeExtendedSingleValue("numberInt", s, true) |
| } else { |
| ejvw.buf = append(ejvw.buf, []byte(s)...) |
| } |
| |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteInt64(i int64) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteInt64"); err != nil { |
| return err |
| } |
| |
| s := strconv.FormatInt(i, 10) |
| |
| if ejvw.canonical { |
| ejvw.writeExtendedSingleValue("numberLong", s, true) |
| } else { |
| ejvw.buf = append(ejvw.buf, []byte(s)...) |
| } |
| |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteJavascript(code string) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteJavascript"); err != nil { |
| return err |
| } |
| |
| var buf bytes.Buffer |
| writeStringWithEscapes(code, &buf, ejvw.escapeHTML) |
| |
| ejvw.writeExtendedSingleValue("code", buf.String(), false) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteMaxKey() error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteMaxKey"); err != nil { |
| return err |
| } |
| |
| ejvw.writeExtendedSingleValue("maxKey", "1", false) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteMinKey() error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteMinKey"); err != nil { |
| return err |
| } |
| |
| ejvw.writeExtendedSingleValue("minKey", "1", false) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteNull() error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteNull"); err != nil { |
| return err |
| } |
| |
| ejvw.buf = append(ejvw.buf, []byte("null")...) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteObjectID(oid primitive.ObjectID) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteObjectID"); err != nil { |
| return err |
| } |
| |
| ejvw.writeExtendedSingleValue("oid", oid.Hex(), true) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteRegex(pattern string, options string) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteRegex"); err != nil { |
| return err |
| } |
| |
| var buf bytes.Buffer |
| buf.WriteString(`{"$regularExpression":{"pattern":`) |
| writeStringWithEscapes(pattern, &buf, ejvw.escapeHTML) |
| buf.WriteString(`,"options":"`) |
| buf.WriteString(sortStringAlphebeticAscending(options)) |
| buf.WriteString(`"}},`) |
| |
| ejvw.buf = append(ejvw.buf, buf.Bytes()...) |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteString(s string) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteString"); err != nil { |
| return err |
| } |
| |
| var buf bytes.Buffer |
| writeStringWithEscapes(s, &buf, ejvw.escapeHTML) |
| |
| ejvw.buf = append(ejvw.buf, buf.Bytes()...) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteSymbol(symbol string) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteSymbol"); err != nil { |
| return err |
| } |
| |
| var buf bytes.Buffer |
| writeStringWithEscapes(symbol, &buf, ejvw.escapeHTML) |
| |
| ejvw.writeExtendedSingleValue("symbol", buf.String(), false) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteTimestamp(t uint32, i uint32) error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteTimestamp"); err != nil { |
| return err |
| } |
| |
| var buf bytes.Buffer |
| buf.WriteString(`{"$timestamp":{"t":`) |
| buf.WriteString(strconv.FormatUint(uint64(t), 10)) |
| buf.WriteString(`,"i":`) |
| buf.WriteString(strconv.FormatUint(uint64(i), 10)) |
| buf.WriteString(`}},`) |
| |
| ejvw.buf = append(ejvw.buf, buf.Bytes()...) |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteUndefined() error { |
| if err := ejvw.ensureElementValue(mode(0), "WriteUndefined"); err != nil { |
| return err |
| } |
| |
| ejvw.writeExtendedSingleValue("undefined", "true", false) |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteDocumentElement(key string) (ValueWriter, error) { |
| switch ejvw.stack[ejvw.frame].mode { |
| case mDocument, mTopLevel, mCodeWithScope: |
| ejvw.buf = append(ejvw.buf, []byte(fmt.Sprintf(`"%s":`, key))...) |
| ejvw.push(mElement) |
| default: |
| return nil, ejvw.invalidTransitionErr(mElement, "WriteDocumentElement", []mode{mDocument, mTopLevel, mCodeWithScope}) |
| } |
| |
| return ejvw, nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteDocumentEnd() error { |
| switch ejvw.stack[ejvw.frame].mode { |
| case mDocument, mTopLevel, mCodeWithScope: |
| default: |
| return fmt.Errorf("incorrect mode to end document: %s", ejvw.stack[ejvw.frame].mode) |
| } |
| |
| // close the document |
| if ejvw.buf[len(ejvw.buf)-1] == ',' { |
| ejvw.buf[len(ejvw.buf)-1] = '}' |
| } else { |
| ejvw.buf = append(ejvw.buf, '}') |
| } |
| |
| switch ejvw.stack[ejvw.frame].mode { |
| case mCodeWithScope: |
| ejvw.buf = append(ejvw.buf, '}') |
| fallthrough |
| case mDocument: |
| ejvw.buf = append(ejvw.buf, ',') |
| case mTopLevel: |
| if ejvw.w != nil { |
| if _, err := ejvw.w.Write(ejvw.buf); err != nil { |
| return err |
| } |
| ejvw.buf = ejvw.buf[:0] |
| } |
| } |
| |
| ejvw.pop() |
| return nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteArrayElement() (ValueWriter, error) { |
| switch ejvw.stack[ejvw.frame].mode { |
| case mArray: |
| ejvw.push(mValue) |
| default: |
| return nil, ejvw.invalidTransitionErr(mValue, "WriteArrayElement", []mode{mArray}) |
| } |
| |
| return ejvw, nil |
| } |
| |
| func (ejvw *extJSONValueWriter) WriteArrayEnd() error { |
| switch ejvw.stack[ejvw.frame].mode { |
| case mArray: |
| // close the array |
| if ejvw.buf[len(ejvw.buf)-1] == ',' { |
| ejvw.buf[len(ejvw.buf)-1] = ']' |
| } else { |
| ejvw.buf = append(ejvw.buf, ']') |
| } |
| |
| ejvw.buf = append(ejvw.buf, ',') |
| |
| ejvw.pop() |
| default: |
| return fmt.Errorf("incorrect mode to end array: %s", ejvw.stack[ejvw.frame].mode) |
| } |
| |
| return nil |
| } |
| |
| func formatDouble(f float64) string { |
| var s string |
| if math.IsInf(f, 1) { |
| s = "Infinity" |
| } else if math.IsInf(f, -1) { |
| s = "-Infinity" |
| } else if math.IsNaN(f) { |
| s = "NaN" |
| } else { |
| // Print exactly one decimalType place for integers; otherwise, print as many are necessary to |
| // perfectly represent it. |
| s = strconv.FormatFloat(f, 'G', -1, 64) |
| if !strings.ContainsRune(s, 'E') && !strings.ContainsRune(s, '.') { |
| s += ".0" |
| } |
| } |
| |
| return s |
| } |
| |
| var hexChars = "0123456789abcdef" |
| |
| func writeStringWithEscapes(s string, buf *bytes.Buffer, escapeHTML bool) { |
| buf.WriteByte('"') |
| start := 0 |
| for i := 0; i < len(s); { |
| if b := s[i]; b < utf8.RuneSelf { |
| if htmlSafeSet[b] || (!escapeHTML && safeSet[b]) { |
| i++ |
| continue |
| } |
| if start < i { |
| buf.WriteString(s[start:i]) |
| } |
| switch b { |
| case '\\', '"': |
| buf.WriteByte('\\') |
| buf.WriteByte(b) |
| case '\n': |
| buf.WriteByte('\\') |
| buf.WriteByte('n') |
| case '\r': |
| buf.WriteByte('\\') |
| buf.WriteByte('r') |
| case '\t': |
| buf.WriteByte('\\') |
| buf.WriteByte('t') |
| case '\b': |
| buf.WriteByte('\\') |
| buf.WriteByte('b') |
| case '\f': |
| buf.WriteByte('\\') |
| buf.WriteByte('f') |
| default: |
| // This encodes bytes < 0x20 except for \t, \n and \r. |
| // If escapeHTML is set, it also escapes <, >, and & |
| // because they can lead to security holes when |
| // user-controlled strings are rendered into JSON |
| // and served to some browsers. |
| buf.WriteString(`\u00`) |
| buf.WriteByte(hexChars[b>>4]) |
| buf.WriteByte(hexChars[b&0xF]) |
| } |
| i++ |
| start = i |
| continue |
| } |
| c, size := utf8.DecodeRuneInString(s[i:]) |
| if c == utf8.RuneError && size == 1 { |
| if start < i { |
| buf.WriteString(s[start:i]) |
| } |
| buf.WriteString(`\ufffd`) |
| i += size |
| start = i |
| continue |
| } |
| // U+2028 is LINE SEPARATOR. |
| // U+2029 is PARAGRAPH SEPARATOR. |
| // They are both technically valid characters in JSON strings, |
| // but don't work in JSONP, which has to be evaluated as JavaScript, |
| // and can lead to security holes there. It is valid JSON to |
| // escape them, so we do so unconditionally. |
| // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. |
| if c == '\u2028' || c == '\u2029' { |
| if start < i { |
| buf.WriteString(s[start:i]) |
| } |
| buf.WriteString(`\u202`) |
| buf.WriteByte(hexChars[c&0xF]) |
| i += size |
| start = i |
| continue |
| } |
| i += size |
| } |
| if start < len(s) { |
| buf.WriteString(s[start:]) |
| } |
| buf.WriteByte('"') |
| } |
| |
| type sortableString []rune |
| |
| func (ss sortableString) Len() int { |
| return len(ss) |
| } |
| |
| func (ss sortableString) Less(i, j int) bool { |
| return ss[i] < ss[j] |
| } |
| |
| func (ss sortableString) Swap(i, j int) { |
| oldI := ss[i] |
| ss[i] = ss[j] |
| ss[j] = oldI |
| } |
| |
| func sortStringAlphebeticAscending(s string) string { |
| ss := sortableString([]rune(s)) |
| sort.Sort(ss) |
| return string([]rune(ss)) |
| } |