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 }