| // Copyright 2013 Google Inc. All Rights Reserved. |
| // |
| // 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. |
| |
| library quiver.strings; |
| |
| /// Returns [true] if [s] is either null, empty or is solely made of whitespace |
| /// characters (as defined by [String.trim]). |
| bool isBlank(String s) => s == null || s.trim().isEmpty; |
| |
| /// Returns [true] if [s] is either null or empty. |
| bool isEmpty(String s) => s == null || s.isEmpty; |
| |
| /// Returns [true] if [s] is a not empty string. |
| bool isNotEmpty(String s) => s != null && s.isNotEmpty; |
| |
| /// Returns a string with characters from the given [s] in reverse order. |
| String reverse(String s) { |
| if (s == null || s == '') return s; |
| StringBuffer sb = new StringBuffer(); |
| var runes = s.runes; |
| for (int i = runes.length - 1; i >= 0; i--) { |
| sb.writeCharCode(runes.elementAt(i)); |
| } |
| return sb.toString(); |
| } |
| |
| /// Returns a string with characters from the given [s] in reverse order. |
| /// |
| /// DEPRECATED: use [reverse] instead. |
| @deprecated |
| String flip(String s) => reverse(s); |
| |
| /// Concatenates [s] to itself a given number of [times]. Empty and null |
| /// strings will always result in empty and null strings respectively no matter |
| /// how many [times] they are [repeat]ed. |
| /// |
| /// If [times] is negative, returns the reversed string repeated given number |
| /// of [times]. |
| /// |
| /// DEPRECATED: use the `*` operator on [String]. |
| @deprecated |
| String repeat(String s, int times) { |
| if (s == null || s == '') return s; |
| if (times < 0) { |
| return repeat(reverse(s), -times); |
| } |
| StringBuffer sink = new StringBuffer(); |
| _repeat(sink, s, times); |
| return sink.toString(); |
| } |
| |
| /// Loops over [s] and returns traversed characters. Takes arbitrary [from] and |
| /// [to] indices. Works as a substitute for [String.substring], except it never |
| /// throws [RangeError]. Supports negative indices. Think of an index as a |
| /// coordinate in an infinite in both directions vector filled with repeating |
| /// string [s], whose 0-th coordinate coincides with the 0-th character in [s]. |
| /// Then [loop] returns the sub-vector defined by the interval ([from], [to]). |
| /// [from] is inclusive. [to] is exclusive. |
| /// |
| /// This method throws exceptions on [null] and empty strings. |
| /// |
| /// If [to] is omitted or is [null] the traversing ends at the end of the loop. |
| /// |
| /// If [to] < [from], traverses [s] in the opposite direction. |
| /// |
| /// For example: |
| /// |
| /// loop('Hello, World!', 7) == 'World!' |
| /// loop('ab', 0, 6) == 'ababab' |
| /// loop('test.txt', -3) == 'txt' |
| /// loop('ldwor', -3, 2) == 'world' |
| String loop(String s, int from, [int to]) { |
| if (s == null || s == '') { |
| throw new ArgumentError('Input string cannot be null or empty'); |
| } |
| if (to != null && to < from) { |
| return loop(reverse(s), -from, -to); |
| } |
| int len = s.length; |
| int leftFrag = from >= 0 ? from ~/ len : ((from - len) ~/ len); |
| if (to == null) { |
| to = (leftFrag + 1) * len; |
| } |
| int rightFrag = to - 1 >= 0 ? to ~/ len : ((to - len) ~/ len); |
| int fragOffset = rightFrag - leftFrag - 1; |
| if (fragOffset == -1) { |
| return s.substring(from - leftFrag * len, to - rightFrag * len); |
| } |
| StringBuffer sink = new StringBuffer(s.substring(from - leftFrag * len)); |
| _repeat(sink, s, fragOffset); |
| sink.write(s.substring(0, to - rightFrag * len)); |
| return sink.toString(); |
| } |
| |
| void _repeat(StringBuffer sink, String s, int times) { |
| for (int i = 0; i < times; i++) { |
| sink.write(s); |
| } |
| } |
| |
| /// Returns `true` if [rune] represents a digit. |
| /// |
| /// The definition of digit matches the Unicode `0x3?` range of Western |
| /// European digits. |
| bool isDigit(int rune) => rune ^ 0x30 <= 9; |
| |
| /// Returns `true` if [rune] represents a whitespace character. |
| /// |
| /// The definition of whitespace matches that used in [String.trim] which is |
| /// based on Unicode 6.2. This maybe be a different set of characters than the |
| /// environment's [RegExp] definition for whitespace, which is given by the |
| /// ECMAScript standard: http://ecma-international.org/ecma-262/5.1/#sec-15.10 |
| bool isWhitespace(int rune) => ((rune >= 0x0009 && rune <= 0x000D) || |
| rune == 0x0020 || |
| rune == 0x0085 || |
| rune == 0x00A0 || |
| rune == 0x1680 || |
| rune == 0x180E || |
| (rune >= 0x2000 && rune <= 0x200A) || |
| rune == 0x2028 || |
| rune == 0x2029 || |
| rune == 0x202F || |
| rune == 0x205F || |
| rune == 0x3000 || |
| rune == 0xFEFF); |
| |
| /// Returns a [String] of length [width] padded with the same number of |
| /// characters on the left and right from [fill]. On the right, characters are |
| /// selected from [fill] starting at the end so that the last character in |
| /// [fill] is the last character in the result. [fill] is repeated if |
| /// neccessary to pad. |
| /// |
| /// Returns [input] if `input.length` is equal to or greater than width. |
| /// [input] can be `null` and is treated as an empty string. |
| /// |
| /// If there are an odd number of characters to pad, then the right will be |
| /// padded with one more than the left. |
| String center(String input, int width, String fill) { |
| if (fill == null || fill.length == 0) { |
| throw new ArgumentError('fill cannot be null or empty'); |
| } |
| if (input == null) input = ''; |
| if (input.length >= width) return input; |
| |
| var padding = width - input.length; |
| if (padding ~/ 2 > 0) { |
| input = loop(fill, 0, padding ~/ 2) + input; |
| } |
| return input + loop(fill, input.length - width, 0); |
| } |
| |
| /// Returns `true` if [a] and [b] are equal after being converted to lower |
| /// case, or are both null. |
| bool equalsIgnoreCase(String a, String b) => |
| (a == null && b == null) || |
| (a != null && b != null && a.toLowerCase() == b.toLowerCase()); |
| |
| /// Compares [a] and [b] after converting to lower case. |
| /// |
| /// Both [a] and [b] must not be null. |
| int compareIgnoreCase(String a, String b) => |
| a.toLowerCase().compareTo(b.toLowerCase()); |