1 /****************************************************************************** 2 3 Escapes characters in a string, that is, prepends '\' to special characters. 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 ******************************************************************************/ 15 16 module ocean.text.util.EscapeChars; 17 18 19 import ocean.transition; 20 21 import ocean.core.Array: concat; 22 import ocean.core.Verify; 23 version (UnitTest) import ocean.core.Test; 24 25 import ocean.stdc.string: strcspn, memmove, memcpy, memchr, strlen; 26 27 /******************************************************************************/ 28 29 struct EscapeChars 30 { 31 /************************************************************************** 32 33 Tokens string consisting of the default special characters to escape 34 35 **************************************************************************/ 36 37 enum Tokens = `"'\`; 38 39 /************************************************************************** 40 41 List of special characters to escape 42 43 **************************************************************************/ 44 45 private mstring tokens; 46 47 /************************************************************************** 48 49 List of occurrences 50 51 **************************************************************************/ 52 53 private size_t[] occurrences; 54 55 /************************************************************************** 56 57 Escapes each occurrence of an element of Tokens in str by inserting 58 the escape pattern escape into str before the occurrence. 59 60 Params: 61 str = string with characters to escape; changed in-place 62 escape = escape pattern to prepend to each token occurrence 63 tokens = List of special characters to escape; empty string 64 indicates to do nothing. '\0' tokens are not allowed. 65 66 Returns: 67 resulting string 68 69 **************************************************************************/ 70 71 public mstring opCall ( ref mstring str, cstring escape = `\`, 72 cstring tokens = Tokens ) 73 { 74 if (tokens.length) 75 { 76 (&this).copyTokens(tokens); 77 78 str ~= '\0'; // append a 0 to the end, as it is stripped in the scope(exit) 79 80 scope (exit) 81 { 82 verify (str.length > 0); 83 verify (!str[$ - 1]); 84 str.length = str.length - 1; 85 enableStomping(str); 86 } 87 88 size_t end = str.length - 1; 89 90 (&this).occurrences.length = 0; 91 enableStomping((&this).occurrences); 92 93 for (size_t pos = strcspn(str.ptr, tokens.ptr); pos < end;) 94 { 95 (&this).occurrences ~= pos; 96 97 pos += strcspn(str.ptr + ++pos, tokens.ptr); 98 } 99 100 str.length = str.length + ((&this).occurrences.length * escape.length); 101 102 str[$ - 1] = '\0'; // append a 0 to the end, as it is stripped in the scope(exit) 103 104 foreach_reverse (i, occurrence; (&this).occurrences) 105 { 106 char* src = str.ptr + occurrence; 107 char* dst = src + ((i + 1) * escape.length); 108 109 memmove(dst, src, end - occurrence); 110 memcpy(dst - escape.length, escape.ptr, escape.length); 111 112 end = occurrence; 113 } 114 } 115 116 return str; 117 } 118 119 /************************************************************************** 120 121 Copies tok to this.tokens and appends a NUL terminator. 122 123 Params: 124 tokens = list of character tokens 125 126 **************************************************************************/ 127 128 private void copyTokens ( cstring tokens ) 129 out 130 { 131 assert ((&this).tokens.length); 132 assert (!(&this).tokens[$ - 1]); 133 assert ((&this).tokens.length - 1 == strlen((&this).tokens.ptr)); 134 } 135 body 136 { 137 verify (tokens.ptr !is null); 138 verify (!memchr(tokens.ptr, '\0', tokens.length), 139 typeof (this).stringof ~ ": NUL characters not allowed in tokens"); 140 this.tokens.concat(tokens, "\0"[]); 141 } 142 } 143 144 unittest 145 { 146 EscapeChars ec; 147 auto to_escape = `Some \ special char`.dup; 148 test!("==")(ec(to_escape), `Some \\ special char`); 149 // Test for stomping prevention 150 test!("==")(ec(to_escape), `Some \\\\ special char`); 151 }