13
13
var parameter_hunter = function ( ) {
14
14
15
15
var extend = require ( 'util' ) . _extend ,
16
+ JSON5 = require ( 'json5' ) ,
16
17
pa = require ( './pattern_assembler' ) ,
17
18
smh = require ( './style_modifier_hunter' ) ,
18
- style_modifier_hunter = new smh ( ) ,
19
- pattern_assembler = new pa ( ) ;
20
-
19
+ pattern_assembler = new pa ( ) ,
20
+ style_modifier_hunter = new smh ( ) ;
21
+
22
+ /**
23
+ * This function is really to accommodate the lax JSON-like syntax allowed by
24
+ * Pattern Lab PHP for parameter submissions to partials. Unfortunately, no
25
+ * easily searchable library was discovered for this. What we had to do was
26
+ * write a custom script to crawl through the parameter string, and wrap the
27
+ * keys and values in double-quotes as necessary.
28
+ * The steps on a high-level are as follows:
29
+ * * Further escape all escaped quotes and colons. Use the string
30
+ * representation of their unicodes for this. This has the added bonus
31
+ * of being interpreted correctly by JSON5.parse() without further
32
+ * modification. This will be useful later in the function.
33
+ * * Once escaped quotes are out of the way, we know the remaining quotes
34
+ * are either key/value wrappers or wrapped within those wrappers. We know
35
+ * that remaining commas and colons are either delimiters, or wrapped
36
+ * within quotes to not be recognized as such.
37
+ * * A do-while loop crawls paramString to write keys to a keys array and
38
+ * values to a values array.
39
+ * * Start by parsing the first key. Determine the type of wrapping quote,
40
+ * if any.
41
+ * * By knowing the open wrapper, we know that the next quote of that kind
42
+ * (if the key is wrapped in quotes), HAS to be the close wrapper.
43
+ * Similarly, if the key is unwrapped, we know the next colon HAS to be
44
+ * the delimiter between key and value.
45
+ * * Save the key to the keys array.
46
+ * * Next, search for a value. It will either be the next block wrapped in
47
+ * quotes, or a string of alphanumerics, decimal points, or minus signs.
48
+ * * Save the value to the values array.
49
+ * * The do-while loop truncates the paramString value while parsing. Its
50
+ * condition for completion is when the paramString is whittled down to an
51
+ * empty string.
52
+ * * After the keys and values arrays are built, a for loop iterates through
53
+ * them to build the final paramStringWellFormed string.
54
+ * * No quote substitution had been done prior to this loop. In this loop,
55
+ * all keys are ensured to be wrapped in double-quotes. String values are
56
+ * also ensured to be wrapped in double-quotes.
57
+ * * Unescape escaped unicodes except for double-quotes. Everything beside
58
+ * double-quotes will be wrapped in double-quotes without need for escape.
59
+ * * Return paramStringWellFormed.
60
+ *
61
+ * @param {string } pString
62
+ * @returns {string } paramStringWellFormed
63
+ */
21
64
function paramToJson ( pString ) {
22
- var paramStringWellFormed = '' ;
23
- var paramStringTmp ;
24
- var colonPos ;
25
- var delimitPos ;
26
- var quotePos ;
27
- var paramString = pString ;
28
-
65
+ var colonPos = - 1 ;
66
+ var keys = [ ] ;
67
+ var paramString = pString ; // to not reassign param
68
+ var paramStringWellFormed ;
69
+ var quotePos = - 1 ;
70
+ var regex ;
71
+ var values = [ ] ;
72
+ var wrapper ;
73
+
74
+ //replace all escaped double-quotes with escaped unicode
75
+ paramString = paramString . replace ( / \\ " / g, '\\u0022' ) ;
76
+
77
+ //replace all escaped single-quotes with escaped unicode
78
+ paramString = paramString . replace ( / \\ ' / g, '\\u0027' ) ;
79
+
80
+ //replace all escaped colons with escaped unicode
81
+ paramString = paramString . replace ( / \\ : / g, '\\u0058' ) ;
82
+
83
+ //with escaped chars out of the way, crawl through paramString looking for
84
+ //keys and values
29
85
do {
30
86
31
- //if param key is wrapped in single quotes, replace with double quotes.
32
- paramString = paramString . replace ( / ( ^ \s * [ \{ | \, ] \s * ) ' ( [ ^ ' ] + ) ' ( \s * \: ) / , '$1"$2"$3' ) ;
87
+ //check if searching for a key
88
+ if ( paramString [ 0 ] === '{' || paramString [ 0 ] === ',' ) {
89
+ paramString = paramString . substring ( 1 , paramString . length ) . trim ( ) ;
33
90
34
- //if params key is not wrapped in any quotes, wrap in double quotes.
35
- paramString = paramString . replace ( / ( ^ \s * [ \{ | \, ] \s * ) ( [ ^ \s " ' \: ] + ) ( \s * \: ) / , '$1"$2"$3' ) ;
91
+ //search for end quote if wrapped in quotes. else search for colon.
92
+ //everything up to that position will be saved in the keys array.
93
+ switch ( paramString [ 0 ] ) {
36
94
37
- //move param key to paramStringWellFormed var.
38
- colonPos = paramString . indexOf ( ':' ) ;
95
+ //need to search for end quote pos in case the quotes wrap a colon
96
+ case '"' :
97
+ case '\'' :
98
+ wrapper = paramString [ 0 ] ;
99
+ quotePos = paramString . indexOf ( wrapper , 1 ) ;
100
+ break ;
39
101
40
- //except to prevent infinite loops.
41
- if ( colonPos === - 1 ) {
42
- colonPos = paramString . length - 1 ;
43
- }
44
- else {
45
- colonPos += 1 ;
46
- }
47
- paramStringWellFormed += paramString . substring ( 0 , colonPos ) ;
48
- paramString = paramString . substring ( colonPos , paramString . length ) . trim ( ) ;
102
+ default :
103
+ colonPos = paramString . indexOf ( ':' ) ;
104
+ }
49
105
50
- //if param value is wrapped in single quotes, replace with double quotes.
51
- if ( paramString [ 0 ] === '\'' ) {
52
- quotePos = paramString . search ( / [ ^ \\ ] ' / ) ;
106
+ if ( quotePos > - 1 ) {
107
+ keys . push ( paramString . substring ( 0 , quotePos + 1 ) . trim ( ) ) ;
53
108
54
- //except for unclosed quotes to prevent infinite loops.
55
- if ( quotePos === - 1 ) {
56
- quotePos = paramString . length - 1 ;
57
- }
58
- else {
59
- quotePos += 2 ;
60
- }
109
+ //truncate the beginning from paramString and look for a value
110
+ paramString = paramString . substring ( quotePos + 1 , paramString . length ) . trim ( ) ;
61
111
62
- //prepare param value for move to paramStringWellFormed var.
63
- paramStringTmp = paramString . substring ( 0 , quotePos ) ;
112
+ //unset quotePos
113
+ quotePos = - 1 ;
64
114
65
- //unescape any escaped single quotes.
66
- paramStringTmp = paramStringTmp . replace ( / \\ ' / g , '\'' ) ;
115
+ } else if ( colonPos > - 1 ) {
116
+ keys . push ( paramString . substring ( 0 , colonPos ) . trim ( ) ) ;
67
117
68
- //escape any double quotes.
69
- paramStringTmp = paramStringTmp . replace ( / " / g , '\\"' ) ;
118
+ //truncate the beginning from paramString and look for a value
119
+ paramString = paramString . substring ( colonPos , paramString . length ) ;
70
120
71
- //replace the delimiting single quotes with double quotes.
72
- paramStringTmp = paramStringTmp . replace ( / ^ ' / , '"' ) ;
73
- paramStringTmp = paramStringTmp . replace ( / ' $ / , '"' ) ;
121
+ //unset colonPos
122
+ colonPos = - 1 ;
74
123
75
- //move param key to paramStringWellFormed var.
76
- paramStringWellFormed += paramStringTmp ;
77
- paramString = paramString . substring ( quotePos , paramString . length ) . trim ( ) ;
124
+ //if there are no more colons, and we're looking for a key, there is
125
+ //probably a problem. stop any further processing.
126
+ } else {
127
+ paramString = '' ;
128
+ break ;
129
+ }
78
130
}
79
131
80
- //if param value is wrapped in double quotes, just move to paramStringWellFormed var.
81
- else if ( paramString [ 0 ] === '"' ) {
82
- quotePos = paramString . search ( / [ ^ \\ ] " / ) ;
83
-
84
- //except for unclosed quotes to prevent infinite loops.
85
- if ( quotePos === - 1 ) {
86
- quotePos = paramString . length - 1 ;
132
+ //now, search for a value
133
+ if ( paramString [ 0 ] === ':' ) {
134
+ paramString = paramString . substring ( 1 , paramString . length ) . trim ( ) ;
135
+
136
+ //the only reason we're using regexes here, instead of indexOf(), is
137
+ //because we don't know if the next delimiter is going to be a comma or
138
+ //a closing curly brace. since it's not much of a performance hit to
139
+ //use regexes as sparingly as here, and it's much more concise and
140
+ //readable, we'll use a regex for match() and replace() instead of
141
+ //performing conditional logic with indexOf().
142
+ switch ( paramString [ 0 ] ) {
143
+
144
+ //since a quote of same type as its wrappers would be escaped, and we
145
+ //escaped those even further with their unicodes, it is safe to look
146
+ //for wrapper pairs and conclude that their contents are values
147
+ case '"' :
148
+ regex = / ^ " ( .| \s ) * ?" / ;
149
+ break ;
150
+ case '\'' :
151
+ regex = / ^ ' ( .| \s ) * ?' / ;
152
+ break ;
153
+
154
+ //if there is no value wrapper, regex for alphanumerics, decimal
155
+ //points, and minus signs for exponential notation.
156
+ default :
157
+ regex = / ^ [ \w \- \. ] * / ;
87
158
}
88
- else {
89
- quotePos += 2 ;
159
+ values . push ( paramString . match ( regex ) [ 0 ] . trim ( ) ) ;
160
+
161
+ //truncate the beginning from paramString and continue either
162
+ //looking for a key, or returning
163
+ paramString = paramString . replace ( regex , '' ) . trim ( ) ;
164
+
165
+ //exit do while if the final char is ' }'
166
+ if ( paramString === '}' ) {
167
+ paramString = '' ;
168
+ break ;
90
169
}
91
170
92
- //move param key to paramStringWellFormed var.
93
- paramStringWellFormed += paramString . substring ( 0 , quotePos ) ;
94
- paramString = paramString . substring ( quotePos , paramString . length ) . trim ( ) ;
171
+ //if there are no more colons, and we're looking for a value, there is
172
+ //probably a problem. stop any further processing.
173
+ } else {
174
+ paramString = '' ;
175
+ break ;
95
176
}
177
+ } while ( paramString ) ;
96
178
97
- //if param value is not wrapped in quotes, move everthing up to the delimiting comma to paramStringWellFormed var.
98
- else {
99
- delimitPos = paramString . indexOf ( ',' ) ;
100
-
101
- //except to prevent infinite loops.
102
- if ( delimitPos === - 1 ) {
103
- delimitPos = paramString . length - 1 ;
179
+ //build paramStringWellFormed string for JSON parsing
180
+ paramStringWellFormed = '{' ;
181
+ for ( var i = 0 ; i < keys . length ; i ++ ) {
182
+
183
+ //keys
184
+ //replace single-quote wrappers with double-quotes
185
+ if ( keys [ i ] [ 0 ] === '\'' && keys [ i ] [ keys [ i ] . length - 1 ] === '\'' ) {
186
+ paramStringWellFormed += '"' ;
187
+
188
+ //any enclosed double-quotes must be escaped
189
+ paramStringWellFormed += keys [ i ] . substring ( 1 , keys [ i ] . length - 1 ) . replace ( / " / g, '\\"' ) ;
190
+ paramStringWellFormed += '"' ;
191
+ } else {
192
+
193
+ //open wrap with double-quotes if no wrapper
194
+ if ( keys [ i ] [ 0 ] !== '"' && keys [ i ] [ 0 ] !== '\'' ) {
195
+ paramStringWellFormed += '"' ;
196
+
197
+ //this is to clean up vestiges from Pattern Lab PHP's escaping scheme.
198
+ //F.Y.I. Pattern Lab PHP would allow special characters like question
199
+ //marks in parameter keys so long as the key was unwrapped and the
200
+ //special character escaped with a backslash. In Node, we need to wrap
201
+ //those keys and unescape those characters.
202
+ keys [ i ] = keys [ i ] . replace ( / \\ / g, '' ) ;
104
203
}
105
- else {
106
- delimitPos += 1 ;
204
+
205
+ paramStringWellFormed += keys [ i ] ;
206
+
207
+ //close wrap with double-quotes if no wrapper
208
+ if ( keys [ i ] [ keys [ i ] . length - 1 ] !== '"' && keys [ i ] [ keys [ i ] . length - 1 ] !== '\'' ) {
209
+ paramStringWellFormed += '"' ;
107
210
}
108
- paramStringWellFormed += paramString . substring ( 0 , delimitPos ) ;
109
- paramString = paramString . substring ( delimitPos , paramString . length ) . trim ( ) ;
110
211
}
111
212
112
- //break at the end.
113
- if ( paramString . length === 1 ) {
114
- paramStringWellFormed += paramString . trim ( ) ;
115
- paramString = '' ;
116
- break ;
213
+ //colon delimiter.
214
+ paramStringWellFormed += ':' ; + values [ i ] ;
215
+
216
+ //values
217
+ //replace single-quote wrappers with double-quotes
218
+ if ( values [ i ] [ 0 ] === '\'' && values [ i ] [ values [ i ] . length - 1 ] === '\'' ) {
219
+ paramStringWellFormed += '"' ;
220
+
221
+ //any enclosed double-quotes must be escaped
222
+ paramStringWellFormed += values [ i ] . substring ( 1 , values [ i ] . length - 1 ) . replace ( / " / g, '\\"' ) ;
223
+ paramStringWellFormed += '"' ;
224
+
225
+ //for everything else, just add the value however it's wrapped
226
+ } else {
227
+ paramStringWellFormed += values [ i ] ;
117
228
}
118
229
119
- } while ( paramString ) ;
230
+ //comma delimiter
231
+ if ( i < keys . length - 1 ) {
232
+ paramStringWellFormed += ',' ;
233
+ }
234
+ }
235
+ paramStringWellFormed += '}' ;
236
+
237
+ //unescape escaped unicode except for double-quotes
238
+ paramStringWellFormed = paramStringWellFormed . replace ( / \\ u 0 0 2 7 / g, '\'' ) ;
239
+ paramStringWellFormed = paramStringWellFormed . replace ( / \\ u 0 0 5 8 / g, ':' ) ;
120
240
121
241
return paramStringWellFormed ;
122
242
}
@@ -140,7 +260,7 @@ var parameter_hunter = function () {
140
260
141
261
//strip out the additional data, convert string to JSON.
142
262
var leftParen = pMatch . indexOf ( '(' ) ;
143
- var rightParen = pMatch . indexOf ( ')' ) ;
263
+ var rightParen = pMatch . lastIndexOf ( ')' ) ;
144
264
var paramString = '{' + pMatch . substring ( leftParen + 1 , rightParen ) + '}' ;
145
265
var paramStringWellFormed = paramToJson ( paramString ) ;
146
266
@@ -149,11 +269,12 @@ var parameter_hunter = function () {
149
269
var localData = { } ;
150
270
151
271
try {
152
- paramData = JSON . parse ( paramStringWellFormed ) ;
153
- globalData = JSON . parse ( JSON . stringify ( patternlab . data ) ) ;
154
- localData = JSON . parse ( JSON . stringify ( pattern . jsonFileData || { } ) ) ;
155
- } catch ( e ) {
156
- console . log ( e ) ;
272
+ paramData = JSON5 . parse ( paramStringWellFormed ) ;
273
+ globalData = JSON5 . parse ( JSON5 . stringify ( patternlab . data ) ) ;
274
+ localData = JSON5 . parse ( JSON5 . stringify ( pattern . jsonFileData || { } ) ) ;
275
+ } catch ( err ) {
276
+ console . log ( 'There was an error parsing JSON for ' + pattern . abspath ) ;
277
+ console . log ( err ) ;
157
278
}
158
279
159
280
var allData = pattern_assembler . merge_data ( globalData , localData ) ;
0 commit comments