github.com/aldelo/common@v1.5.1/helper-str.go (about) 1 package helper 2 3 /* 4 * Copyright 2020-2023 Aldelo, LP 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 import ( 20 "encoding/base64" 21 "encoding/hex" 22 "encoding/json" 23 "encoding/xml" 24 "fmt" 25 "github.com/aldelo/common/ascii" 26 "html" 27 "regexp" 28 "strings" 29 ) 30 31 // LenTrim returns length of space trimmed string s 32 func LenTrim(s string) int { 33 return len(strings.TrimSpace(s)) 34 } 35 36 // NextFixedLength calculates the next fixed length total block size, 37 // for example, if block size is 16, then the total size should be 16, 32, 48 and so on based on data length 38 func NextFixedLength(data string, blockSize int) int { 39 blocks := (len(data) / blockSize) + 1 40 blocks = blocks * blockSize 41 42 return blocks 43 } 44 45 // Left returns the left side of string indicated by variable l (size of substring) 46 func Left(s string, l int) string { 47 if len(s) <= l { 48 return s 49 } 50 51 if l <= 0 { 52 return s 53 } 54 55 return s[0:l] 56 } 57 58 // Right returns the right side of string indicated by variable l (size of substring) 59 func Right(s string, l int) string { 60 if len(s) <= l { 61 return s 62 } 63 64 if l <= 0 { 65 return s 66 } 67 68 return s[len(s)-l:] 69 } 70 71 // Mid returns the middle of string indicated by variable start and l positions (size of substring) 72 func Mid(s string, start int, l int) string { 73 if len(s) <= l { 74 return s 75 } 76 77 if l <= 0 { 78 return s 79 } 80 81 if start > len(s)-1 { 82 return s 83 } 84 85 if (len(s) - start) < l { 86 return s 87 } 88 89 return s[start : l+start] 90 } 91 92 // Reverse a string 93 func Reverse(s string) string { 94 chars := []rune(s) 95 for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 { 96 chars[i], chars[j] = chars[j], chars[i] 97 } 98 return string(chars) 99 } 100 101 // Replace will replace old char with new char and return the replaced string 102 func Replace(s string, oldChar string, newChar string) string { 103 return strings.Replace(s, oldChar, newChar, -1) 104 } 105 106 // Trim gets the left and right space trimmed input string s 107 func Trim(s string) string { 108 return strings.TrimSpace(s) 109 } 110 111 // RightTrimLF will remove linefeed (return char) from the right most char and return result string 112 func RightTrimLF(s string) string { 113 if LenTrim(s) > 0 { 114 if Right(s, 2) == "\r\n" { 115 return Left(s, len(s)-2) 116 } 117 118 if Right(s, 1) == "\n" { 119 return Left(s, len(s)-1) 120 } 121 } 122 123 return s 124 } 125 126 // Padding will pad the data with specified char either to the left or right 127 func Padding(data string, totalSize int, padRight bool, padChar string) string { 128 var result string 129 result = data 130 131 b := []byte(data) 132 diff := totalSize - len(b) 133 134 if diff > 0 { 135 var pChar string 136 137 if len(padChar) == 0 { 138 pChar = " " 139 } else { 140 pChar = string(padChar[0]) 141 } 142 143 pad := strings.Repeat(pChar, diff) 144 145 if padRight { 146 result += pad 147 } else { 148 result = pad + result 149 } 150 } 151 152 // return result 153 return result 154 } 155 156 // PadLeft will pad data with space to the left 157 func PadLeft(data string, totalSize int) string { 158 return Padding(data, totalSize, false, " ") 159 } 160 161 // PadRight will pad data with space to the right 162 func PadRight(data string, totalSize int) string { 163 return Padding(data, totalSize, true, " ") 164 } 165 166 // PadZero pads zero to the left by default 167 func PadZero(data string, totalSize int, padRight ...bool) string { 168 right := GetFirstBoolOrDefault(false, padRight...) 169 return Padding(data, totalSize, right, "0") 170 } 171 172 // PadX pads X to the left by default 173 func PadX(data string, totalSize int, padRight ...bool) string { 174 right := GetFirstBoolOrDefault(false, padRight...) 175 return Padding(data, totalSize, right, "X") 176 } 177 178 // SplitString will split the source string using delimiter, and return the element indicated by index, 179 // if nothing is found, blank is returned, 180 // index = -1 returns last index 181 func SplitString(source string, delimiter string, index int) string { 182 ar := strings.Split(source, delimiter) 183 184 if len(ar) > 0 { 185 if index <= -1 { 186 return ar[len(ar)-1] 187 } else { 188 if len(ar) > index { 189 return ar[index] 190 } else { 191 return "" 192 } 193 } 194 } 195 196 return "" 197 } 198 199 // SliceStringToCSVString unboxes slice of string into comma separated string 200 func SliceStringToCSVString(source []string, spaceAfterComma bool) string { 201 output := "" 202 203 for _, v := range source { 204 if LenTrim(output) > 0 { 205 output += "," 206 207 if spaceAfterComma { 208 output += " " 209 } 210 } 211 212 output += v 213 } 214 215 return output 216 } 217 218 // ParseKeyValue will parse the input string using specified delimiter (= is default), 219 // result is set in the key and val fields 220 func ParseKeyValue(s string, delimiter string, key *string, val *string) error { 221 if len(s) <= 2 { 222 *key = "" 223 *val = "" 224 return fmt.Errorf("Source Data Must Exceed 2 Characters") 225 } 226 227 if len(delimiter) == 0 { 228 delimiter = "=" 229 } else { 230 delimiter = string(delimiter[0]) 231 } 232 233 if strings.Contains(s, delimiter) { 234 p := strings.Split(s, delimiter) 235 236 if len(p) == 2 { 237 *key = Trim(p[0]) 238 *val = Trim(p[1]) 239 return nil 240 } 241 242 // parts not valid 243 *key = "" 244 *val = "" 245 return fmt.Errorf("Parsed Parts Must Equal 2") 246 } 247 248 // no delimiter found 249 *key = "" 250 *val = "" 251 return fmt.Errorf("Delimiter Not Found in Source Data") 252 } 253 254 // ExtractNumeric will extract only 0-9 out of string to be returned 255 func ExtractNumeric(s string) (string, error) { 256 exp, err := regexp.Compile("[^0-9]+") 257 258 if err != nil { 259 return "", err 260 } 261 262 return exp.ReplaceAllString(s, ""), nil 263 } 264 265 // ExtractAlpha will extract A-Z out of string to be returned 266 func ExtractAlpha(s string) (string, error) { 267 exp, err := regexp.Compile("[^A-Za-z]+") 268 269 if err != nil { 270 return "", err 271 } 272 273 return exp.ReplaceAllString(s, ""), nil 274 } 275 276 // ExtractAlphaNumeric will extract only A-Z, a-z, and 0-9 out of string to be returned 277 func ExtractAlphaNumeric(s string) (string, error) { 278 exp, err := regexp.Compile("[^A-Za-z0-9]+") 279 280 if err != nil { 281 return "", err 282 } 283 284 return exp.ReplaceAllString(s, ""), nil 285 } 286 287 // ExtractHex will extract only A-F, a-f, and 0-9 out of string to be returned 288 func ExtractHex(s string) (string, error) { 289 exp, err := regexp.Compile("[^A-Fa-f0-9]+") 290 291 if err != nil { 292 return "", err 293 } 294 295 return exp.ReplaceAllString(s, ""), nil 296 } 297 298 // ExtractAlphaNumericUnderscoreDash will extract only A-Z, a-z, 0-9, _, - out of string to be returned 299 func ExtractAlphaNumericUnderscoreDash(s string) (string, error) { 300 exp, err := regexp.Compile("[^A-Za-z0-9_-]+") 301 302 if err != nil { 303 return "", err 304 } 305 306 return exp.ReplaceAllString(s, ""), nil 307 } 308 309 // ExtractAlphaNumericPrintableSymbols will extra A-Z, a-z, 0-9, and printable symbols 310 func ExtractAlphaNumericPrintableSymbols(s string) (string, error) { 311 exp, err := regexp.Compile("[^ -~]+") 312 313 if err != nil { 314 return "", err 315 } 316 317 return exp.ReplaceAllString(s, ""), nil 318 } 319 320 // ExtractByRegex will extract string based on regex expression, 321 // any regex match will be replaced with blank 322 func ExtractByRegex(s string, regexStr string) (string, error) { 323 exp, err := regexp.Compile(regexStr) 324 325 if err != nil { 326 return "", err 327 } 328 329 return exp.ReplaceAllString(s, ""), nil 330 } 331 332 // ================================================================================================================ 333 // TYPE CHECK HELPERS 334 // ================================================================================================================ 335 336 // IsAlphanumericOnly checks if the input string is A-Z, a-z, and 0-9 only 337 func IsAlphanumericOnly(s string) bool { 338 exp, err := regexp.Compile("[A-Za-z0-9]+") 339 340 if err != nil { 341 return false 342 } 343 344 if len(exp.ReplaceAllString(s, "")) > 0 { 345 // has non alphanumeric 346 return false 347 } else { 348 // alphanumeric only 349 return true 350 } 351 } 352 353 // IsAlphanumericAndSpaceOnly checks if the input string is A-Z, a-z, 0-9, and space 354 func IsAlphanumericAndSpaceOnly(s string) bool { 355 exp, err := regexp.Compile("[A-Za-z0-9 ]+") 356 357 if err != nil { 358 return false 359 } 360 361 if len(exp.ReplaceAllString(s, "")) > 0 { 362 // has non alphanumeric and space 363 return false 364 } else { 365 // alphanumeric and space only 366 return true 367 } 368 } 369 370 // IsBase64Only checks if the input string is a-z, A-Z, 0-9, +, /, = 371 func IsBase64Only(s string) bool { 372 exp, err := regexp.Compile("[A-Za-z0-9+/=]+") 373 374 if err != nil { 375 return false 376 } 377 378 if len(exp.ReplaceAllString(s, "")) > 0 { 379 // has non base 64 380 return false 381 } else { 382 // base 64 only 383 return true 384 } 385 } 386 387 // IsHexOnly checks if the input string is a-f, A-F, 0-9 388 func IsHexOnly(s string) bool { 389 exp, err := regexp.Compile("[A-Fa-f0-9]+") 390 391 if err != nil { 392 return false 393 } 394 395 if len(exp.ReplaceAllString(s, "")) > 0 { 396 // has non hex 397 return false 398 } else { 399 // hex only 400 return true 401 } 402 } 403 404 // IsNumericIntOnly checks if the input string is 0-9 only 405 func IsNumericIntOnly(s string) bool { 406 exp, err := regexp.Compile("[0-9]+") 407 408 if err != nil { 409 return false 410 } 411 412 if len(exp.ReplaceAllString(s, "")) > 0 { 413 // has non numeric 414 return false 415 } else { 416 // numeric only 417 return true 418 } 419 } 420 421 // IsNumericFloat64 checks if string is float 422 func IsNumericFloat64(s string, positiveOnly bool) bool { 423 if LenTrim(s) == 0 { 424 return false 425 } 426 427 if f, ok := ParseFloat64(s); !ok { 428 return false 429 } else { 430 if positiveOnly { 431 if f < 0 { 432 return false 433 } else { 434 return true 435 } 436 } else { 437 return true 438 } 439 } 440 } 441 442 // IsNumericIntAndNegativeSignOnly checks if the input string is 0-9 and possibly with lead negative sign only 443 func IsNumericIntAndNegativeSignOnly(s string) bool { 444 if len(s) == 0 { 445 return false 446 } 447 448 if !IsNumericIntOnly(Left(s, 1)) && Left(s, 1) != "-" { 449 return false 450 } 451 452 if len(s) > 1 { 453 v := Right(s, len(s)-1) 454 455 if !IsNumericIntOnly(v) { 456 return false 457 } else { 458 return true 459 } 460 } else { 461 if s == "-" { 462 return false 463 } else { 464 return true 465 } 466 } 467 } 468 469 // ================================================================================================================ 470 // HEX HELPERS 471 // ================================================================================================================ 472 473 // StringToHex converts string into hex 474 func StringToHex(data string) string { 475 return strings.ToUpper(hex.EncodeToString([]byte(data))) 476 } 477 478 // ByteToHex converts byte array into hex 479 func ByteToHex(data []byte) string { 480 return strings.ToUpper(hex.EncodeToString(data)) 481 } 482 483 // HexToString converts hex data into string 484 func HexToString(hexData string) (string, error) { 485 data, err := hex.DecodeString(hexData) 486 487 if err != nil { 488 return "", err 489 } 490 491 return string(data), nil 492 } 493 494 // HexToByte converts hex data into byte array 495 func HexToByte(hexData string) ([]byte, error) { 496 data, err := hex.DecodeString(hexData) 497 498 if err != nil { 499 return []byte{}, err 500 } 501 502 return data, nil 503 } 504 505 // ================================================================================================================ 506 // BASE64 HELPERS 507 // ================================================================================================================ 508 509 // Base64StdEncode will encode given data into base 64 standard encoded string 510 func Base64StdEncode(data string) string { 511 return base64.StdEncoding.EncodeToString([]byte(data)) 512 } 513 514 // Base64StdDecode will decode given data from base 64 standard encoded string 515 func Base64StdDecode(data string) (string, error) { 516 b, err := base64.StdEncoding.DecodeString(data) 517 518 if err != nil { 519 return "", err 520 } 521 522 return string(b), nil 523 } 524 525 // Base64UrlEncode will encode given data into base 64 url encoded string 526 func Base64UrlEncode(data string) string { 527 return base64.URLEncoding.EncodeToString([]byte(data)) 528 } 529 530 // Base64UrlDecode will decode given data from base 64 url encoded string 531 func Base64UrlDecode(data string) (string, error) { 532 b, err := base64.URLEncoding.DecodeString(data) 533 534 if err != nil { 535 return "", err 536 } 537 538 return string(b), nil 539 } 540 541 // ================================================================================================================ 542 // HTML HELPERS 543 // ================================================================================================================ 544 545 // HTMLDecode will unescape html tags and extended tags relevant to our apps 546 func HTMLDecode(s string) string { 547 buf := html.UnescapeString(s) 548 549 buf = strings.Replace(buf, "&#39;", "'", -1) 550 buf = strings.Replace(buf, "&lt;", "<", -1) 551 buf = strings.Replace(buf, "&gt;", ">", -1) 552 buf = strings.Replace(buf, "&quot;", "\"", -1) 553 buf = strings.Replace(buf, "&apos;", "'", -1) 554 buf = strings.Replace(buf, "&#169;", "©", -1) 555 buf = strings.Replace(buf, "'", "'", -1) 556 buf = strings.Replace(buf, "<", "<", -1) 557 buf = strings.Replace(buf, ">", ">", -1) 558 buf = strings.Replace(buf, """, "\"", -1) 559 buf = strings.Replace(buf, "'", "'", -1) 560 buf = strings.Replace(buf, "©", "©", -1) 561 buf = strings.Replace(buf, "<FS>", "=", -1) 562 buf = strings.Replace(buf, "<GS>", "\n", -1) 563 564 return buf 565 } 566 567 // HTMLEncode will escape html tags 568 func HTMLEncode(s string) string { 569 return html.EscapeString(s) 570 } 571 572 // ================================================================================================================ 573 // XML HELPERS 574 // ================================================================================================================ 575 576 // XMLToEscaped will escape the data whose xml special chars > < & % ' " are escaped into > < & % ' " 577 func XMLToEscaped(data string) string { 578 var r string 579 580 r = strings.Replace(data, "&", "&", -1) 581 r = strings.Replace(r, ">", ">", -1) 582 r = strings.Replace(r, "<", "<", -1) 583 r = strings.Replace(r, "%", "%", -1) 584 r = strings.Replace(r, "'", "'", -1) 585 r = strings.Replace(r, "\"", """, -1) 586 587 return r 588 } 589 590 // XMLFromEscaped will un-escape the data whose > < & % ' " are converted to > < & % ' " 591 func XMLFromEscaped(data string) string { 592 var r string 593 594 r = strings.Replace(data, "&", "&", -1) 595 r = strings.Replace(r, ">", ">", -1) 596 r = strings.Replace(r, "<", "<", -1) 597 r = strings.Replace(r, "%", "%", -1) 598 r = strings.Replace(r, "'", "'", -1) 599 r = strings.Replace(r, """, "\"", -1) 600 601 return r 602 } 603 604 // MarshalXMLCompact will accept an input variable, typically struct with xml struct tags, to serialize from object into xml string 605 // 606 // *** STRUCT FIELDS MUST BE EXPORTED FOR MARSHAL AND UNMARSHAL *** 607 // 608 // special struct field: 609 // 610 // XMLName xml.Name `xml:"ElementName"` 611 // 612 // struct xml tags: 613 // 614 // `xml:"AttributeName,attr"` or `xml:",attr"` <<< Attribute Instead of Element 615 // `xml:"ElementName"` <<< XML Element Name 616 // `xml:"OuterElementName>InnerElementName"` <<< Outer XML Grouping By OuterElementName 617 // `xml:",cdata"` <<< <![CDATA[...]] 618 // `xml:",innerxml"` <<< Write as Inner XML Verbatim and Not Subject to Marshaling 619 // `xml:",comment"` <<< Write as Comment, and Not Contain "--" Within Value 620 // `xml:"...,omitempty"` <<< Omit This Line if Empty Value (false, 0, nil, zero length array) 621 // `xml:"-"` <<< Omit From XML Marshal 622 func MarshalXMLCompact(v interface{}) (string, error) { 623 if v == nil { 624 return "", fmt.Errorf("Object For XML Marshal Must Not Be Nil") 625 } 626 627 b, err := xml.Marshal(v) 628 629 if err != nil { 630 return "", err 631 } 632 633 return string(b), nil 634 } 635 636 // MarshalXMLIndent will accept an input variable, typically struct with xml struct tags, to serialize from object into xml string with indented formatting 637 // 638 // *** STRUCT FIELDS MUST BE EXPORTED FOR MARSHAL AND UNMARSHAL *** 639 // 640 // special struct field: 641 // 642 // XMLName xml.Name `xml:"ElementName"` 643 // 644 // struct xml tags: 645 // 646 // `xml:"AttributeName,attr"` or `xml:",attr"` <<< Attribute Instead of Element 647 // `xml:"ElementName"` <<< XML Element Name 648 // `xml:"OuterElementName>InnerElementName"` <<< Outer XML Grouping By OuterElementName 649 // `xml:",cdata"` <<< <![CDATA[...]] 650 // `xml:",innerxml"` <<< Write as Inner XML Verbatim and Not Subject to Marshaling 651 // `xml:",comment"` <<< Write as Comment, and Not Contain "--" Within Value 652 // `xml:"...,omitempty"` <<< Omit This Line if Empty Value (false, 0, nil, zero length array) 653 // `xml:"-"` <<< Omit From XML Marshal 654 func MarshalXMLIndent(v interface{}) (string, error) { 655 if v == nil { 656 return "", fmt.Errorf("Object For XML Marshal Must Not Be Nil") 657 } 658 659 b, err := xml.MarshalIndent(v, "", " ") 660 661 if err != nil { 662 return "", err 663 } 664 665 return string(b), nil 666 } 667 668 // MarshalXML with option for indent or compact 669 func MarshalXML(v interface{}, indentXML bool) (string, error) { 670 if indentXML { 671 return MarshalXMLIndent(v) 672 } else { 673 return MarshalXMLCompact(v) 674 } 675 } 676 677 // UnmarshalXML will accept input xml data string and deserialize into target object indicated by parameter v 678 // 679 // *** PASS PARAMETER AS "&v" IN ORDER TO BE WRITABLE *** 680 // 681 // *** STRUCT FIELDS MUST BE EXPORTED FOR MARSHAL AND UNMARSHAL *** 682 // 683 // if unmarshal is successful, nil is returned, otherwise error info is returned 684 func UnmarshalXML(xmlData string, v interface{}) error { 685 if LenTrim(xmlData) == 0 { 686 return fmt.Errorf("XML Data is Required") 687 } 688 689 return xml.Unmarshal([]byte(xmlData), v) 690 } 691 692 // ================================================================================================================ 693 // ENCODING JSON HELPERS 694 // ================================================================================================================ 695 696 // JsonToEscaped will escape the data whose json special chars are escaped 697 func JsonToEscaped(data string) string { 698 var r string 699 700 r = strings.Replace(data, `\`, `\\`, -1) 701 r = ascii.EscapeNonPrintable(r) 702 703 return r 704 } 705 706 // JsonFromEscaped will unescape the json data that may be special character escaped 707 func JsonFromEscaped(data string) string { 708 var r string 709 710 r = strings.Replace(data, `\\`, `\`, -1) 711 r = ascii.UnescapeNonPrintable(r) 712 713 if Left(r, 1) == "\"" { 714 r = Right(r, len(r)-1) 715 } 716 717 if Right(r, 1) == "\"" { 718 r = Left(r, len(r)-1) 719 } 720 721 return r 722 } 723 724 // MarshalJSONCompact will accept an input variable, typically struct with json struct tags, to serialize from object into json string with compact formatting 725 // 726 // *** STRUCT FIELDS MUST BE EXPORTED FOR MARSHAL AND UNMARSHAL *** 727 // 728 // struct json tags: 729 // 730 // `json:"ElementName"` <<< JSON Element Name 731 // `json:"...,omitempty"` <<< Omit This Line if Empty Value (false, 0, nil, zero length array) 732 // `json:"-"` <<< Omit From JSON Marshal 733 func MarshalJSONCompact(v interface{}) (string, error) { 734 if v == nil { 735 return "", fmt.Errorf("Object For JSON Marshal Must Not Be Nil") 736 } 737 738 b, err := json.Marshal(v) 739 740 if err != nil { 741 return "", err 742 } 743 744 return string(b), nil 745 } 746 747 // MarshalJSONIndent will accept an input variable, typically struct with json struct tags, to serialize from object into json string with indented formatting 748 // 749 // *** STRUCT FIELDS MUST BE EXPORTED FOR MARSHAL AND UNMARSHAL *** 750 // 751 // struct json tags: 752 // 753 // `json:"ElementName"` <<< JSON Element Name 754 // `json:"...,omitempty"` <<< Omit This Line if Empty Value (false, 0, nil, zero length array) 755 // `json:"-"` <<< Omit From JSON Marshal 756 func MarshalJSONIndent(v interface{}) (string, error) { 757 if v == nil { 758 return "", fmt.Errorf("Object For JSON Marshal Must Not Be Nil") 759 } 760 761 b, err := json.MarshalIndent(v, "", " ") 762 763 if err != nil { 764 return "", err 765 } 766 767 return string(b), nil 768 } 769 770 // UnmarshalJSON will accept input json data string and deserialize into target object indicated by parameter v 771 // 772 // *** PASS PARAMETER AS "&v" IN ORDER TO BE WRITABLE *** 773 // *** v interface{} MUST BE initialized first *** 774 // *** STRUCT FIELDS MUST BE EXPORTED FOR MARSHAL AND UNMARSHAL *** 775 // 776 // if unmarshal is successful, nil is returned, otherwise error info is returned 777 func UnmarshalJSON(jsonData string, v interface{}) error { 778 if LenTrim(jsonData) == 0 { 779 return fmt.Errorf("JSON Data is Required") 780 } 781 782 return json.Unmarshal([]byte(jsonData), v) 783 }