+ @VisibleForTesting
+ static String trimWhitespace(final String str, final int dquot) {
+ int brk = str.indexOf('\n');
+ if (brk == -1) {
+ // No need to trim whitespace
+ return str;
+ }
+
+ // Okay, we may need to do some trimming, set up a builder and append the first segment
+ final int length = str.length();
+ final StringBuilder sb = new StringBuilder(length);
+
+ // Append first segment, which needs only tail-trimming
+ sb.append(str, 0, trimTrailing(str, 0, brk)).append('\n');
+
+ // With that out of the way, setup our iteration state. The string segment we are looking at is
+ // str.substring(start, end), which is guaranteed not to include any line breaks, i.e. end <= brk unless we are
+ // at the last segment.
+ int start = brk + 1;
+ brk = str.indexOf('\n', start);
+
+ // Loop over inner strings
+ while (brk != -1) {
+ final int end = brk != -1 ? brk : length;
+ trimLeadingAndAppend(sb, dquot, str, start, trimTrailing(str, start, end)).append('\n');
+ start = end + 1;
+ brk = str.indexOf('\n', start);
+ }
+
+ return trimLeadingAndAppend(sb, dquot, str, start, length).toString();
+ }
+
+ private static StringBuilder trimLeadingAndAppend(final StringBuilder sb, final int dquot, final String str,
+ final int start, final int end) {
+ int offset = start;
+ int pos = 0;
+
+ while (pos <= dquot) {
+ if (offset == end) {
+ // We ran out of data, nothing to append
+ return sb;
+ }
+
+ final char ch = str.charAt(offset);
+ if (ch == '\t') {
+ // tabs are to be treated as 8 spaces
+ pos += 8;
+ } else if (WHITESPACE.matches(ch)) {
+ pos++;
+ } else {
+ break;
+ }
+
+ offset++;
+ }
+
+ // We have expanded beyond double quotes, push equivalent spaces
+ while (pos - 1 > dquot) {
+ sb.append(' ');
+ pos--;
+ }
+
+ return sb.append(str, offset, end);
+ }
+
+ private static int trimTrailing(final String str, final int start, final int end) {
+ int ret = end;
+ while (ret > start) {
+ final int prev = ret - 1;
+ if (!WHITESPACE.matches(str.charAt(prev))) {
+ break;
+ }
+ ret = prev;
+ }
+ return ret;
+ }
+