Skip to content

Commit 4dd49d1

Browse files
committed
Perf improvements, cfquery sql attr fix
1 parent fbb468c commit 4dd49d1

9 files changed

Lines changed: 168 additions & 26 deletions

File tree

.github/workflows/ci.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
8+
9+
workflow_dispatch:
10+
11+
env:
12+
TEST_RUNNER_URI: /tests/run.cfm?reporter=text
13+
14+
jobs:
15+
16+
cfmatrix:
17+
runs-on: ubuntu-latest
18+
permissions:
19+
contents: read
20+
21+
#setup the matrix of CF Engines
22+
strategy:
23+
matrix:
24+
cfengine: ["lucee@5", "lucee@6", "adobe@2025"]
25+
env:
26+
CFENGINE: ${{ matrix.cfengine }}
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Clone cfmatrix
31+
run: git clone --depth 1 https://github.com/foundeo/cfmatrix.git cfmatrix
32+
33+
- name: Install cfmatrix
34+
run: bash ./cfmatrix/install.sh
35+
36+
- name: Run Tests
37+
run: bash ./cfmatrix/run.sh

File.cfc

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ component {
1616
variables.fileLength = len(variables.fileContent);
1717

1818
if (arguments.parser == "detect") {
19-
local.hasScriptComponentPattern = reFindNoCase("component[^>*]*{", variables.fileContent);
19+
local.hasScriptComponentPattern = reFind("component[^>*]*{", getFileContentLowerCase());
2020
if (local.hasScriptComponentPattern) {
2121
local.componentString = mid(variables.fileContent, local.hasScriptComponentPattern, 10);
2222
//must be component followed by space or {
2323
local.hasScriptComponentPattern = trim(local.componentString) == "component" || local.componentString == "component{";
2424
}
25-
local.hasTagComponentPattern = !findNoCase("<" & "cfcomponent", variables.fileContent);
25+
local.hasTagComponentPattern = !find("<" & "cfcomponent", getFileContentLowerCase());
2626
if (local.hasScriptComponentPattern && !local.hasTagComponentPattern) {
2727
//script cfc
2828
variables.isScript = true;
@@ -31,12 +31,12 @@ component {
3131
} else if (local.hasTagComponentPattern && local.hasScriptComponentPattern) {
3232

3333
//possible that cfcomponent it could be in a comment
34-
if (reFindNoCase("//[^\n]*cfcomponent[^\n]*[\n]", variables.fileContent)) {
34+
if (reFind("//[^\n]*cfcomponent[^\n]*[\n]", getFileContentLowerCase())) {
3535
variables.isScript = true;
3636
}
3737

38-
else if (!reFindNoCase("<" & "cffunction", variables.fileContent) && !reFindNoCase("<" & "cfproperty", variables.fileContent)) {
39-
//if it does not have a cffunction or cfproperty assume scritp
38+
else if (!find("<" & "cffunction", getFileContentLowerCase()) && !find("<" & "cfproperty", getFileContentLowerCase())) {
39+
//if it does not have a cffunction or cfproperty assume script
4040
variables.isScript = true;
4141
} else {
4242
variables.isScript=false;
@@ -76,6 +76,13 @@ component {
7676
return variables.fileContent;
7777
}
7878

79+
function getFileContentLowerCase() {
80+
if (!variables.keyExists("fileContentLowerCase")) {
81+
variables.fileContentLowerCase = lCase(getFileContent());
82+
}
83+
return variables.fileContentLowerCase;
84+
}
85+
7986
function getFilePath() {
8087
return variables.filePath;
8188
}
@@ -155,7 +162,6 @@ component {
155162

156163
public boolean function hasStatementAtPosition(numeric pos) {
157164
var s = "";
158-
var stmts = [];
159165
for (s in getStatements()) {
160166
if (s.getStartPosition() <= arguments.pos && s.getEndPosition() >=pos) {
161167
return true;

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
A CFML Parser written in CFML
44

5-
[![Build Status](https://travis-ci.org/foundeo/cfmlparser.svg?branch=master)](https://travis-ci.org/foundeo/cfmlparser)
5+
![CI](https://github.com/foundeo/cfmlparser/actions/workflows/ci.yml/badge.svg)
6+
67

78
## Basic Usage
89

ScriptParser.cfc

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ component extends="AbstractParser" {
2525
var currentStatement = "";
2626
var currentStatementStart = 1;
2727
var commentStatement = "";
28+
var lowerCaseContent = arguments.file.getFileContentLowerCase();
2829
var sb = createObject("java", "java.lang.StringBuilder");
2930

3031
//parsing a cfscript tag uses startPosition and endPosition
3132
if (arguments.startPosition != 0 && arguments.endPosition != 0) {
3233
pos = arguments.startPosition;
34+
scriptTagContent = mid(content, pos, contentLength-arguments.endPosition+1);
3335
contentLength = arguments.endPosition;
3436
}
3537

@@ -181,7 +183,7 @@ component extends="AbstractParser" {
181183
pos = pos+9;
182184
currentState = this.STATE.COMPONENT_STATEMENT;
183185
continue;
184-
} else if (lowerC == "f" && reFindNoCase("function[\t\r\n a-zA-Z_]", mid(content, pos, 9)) ) {
186+
} else if (lowerC == "f" && reFind("function[\t\r\n a-zA-Z_]", mid(lowerCaseContent, pos, 9)) ) {
185187
//a function without access modifier or return type
186188
sb.append(mid(content, pos, 8));
187189
currentState = this.STATE.FUNCTION_STATEMENT;
@@ -192,7 +194,7 @@ component extends="AbstractParser" {
192194
}
193195
pos = pos + 8;
194196
continue;
195-
} else if (lowerC == "i" && reFindNoCase("if[\t\r\n (]", mid(content, pos, 3))) {
197+
} else if (lowerC == "i" && reFind("if[\t\r\n (]", mid(lowerCaseContent, pos, 3))) {
196198
currentStatementStart = pos;
197199
currentStatement = new ScriptStatement(name="if", startPosition=pos, file=arguments.file, parent=parent);
198200
parent = currentStatement;
@@ -205,7 +207,7 @@ component extends="AbstractParser" {
205207
sb.append(mid(content, pos, 2));
206208
pos = pos+2;
207209
continue;
208-
} else if (lowerC == "e" && reFindNoCase("else[ \t\r\n]+if[\t\r\n (]", content, pos) == pos) {
210+
} else if (lowerC == "e" && reFind("else[ \t\r\n]+if[\t\r\n (]", lowerCaseContent, pos) == pos) {
209211
currentStatementStart = pos;
210212
currentStatement = new ScriptStatement(name="else if", startPosition=pos, file=arguments.file, parent=parent);
211213
currentState = this.STATE.ELSE_IF_STATEMENT;
@@ -218,7 +220,7 @@ component extends="AbstractParser" {
218220
sb.append(mid(content, pos, paren-pos));
219221
pos = paren;
220222
continue;
221-
} else if (lowerC == "e" && reFindNoCase("else[\t\r\n (]", content, pos) == pos) {
223+
} else if (lowerC == "e" && reFind("else[\t\r\n (]", lowerCaseContent, pos) == pos) {
222224
currentStatementStart = pos;
223225
currentStatement = new ScriptStatement(name="else", startPosition=pos, file=arguments.file, parent=parent);
224226
parent = currentStatement;
@@ -241,7 +243,7 @@ component extends="AbstractParser" {
241243
sb.append("var ");
242244
pos = pos + 4;
243245
continue;
244-
} else if (lowerC == "r" && reFindNoCase("return[\t\r\n ;]", mid(content, pos, 7)) == pos) {
246+
} else if (lowerC == "r" && reFind("return[\t\r\n ;]", mid(lowerCaseContent, pos, 7)) == pos) {
245247
currentStatement = new ScriptStatement(name="return", startPosition=pos, file=arguments.file, parent=parent);
246248
currentState = this.STATE.RETURN_STATEMENT;
247249
addStatement(currentStatement);
@@ -251,7 +253,7 @@ component extends="AbstractParser" {
251253
sb.append(mid(content, pos, 6));
252254
pos = pos + 6;
253255
continue;
254-
} else if (lowerC == "f" && reFindNoCase("for\s*\(", content, pos) == pos) {
256+
} else if (lowerC == "f" && reFind("for\s*\(", lowerCaseContent, pos) == pos) {
255257
currentStatementStart = pos;
256258
currentStatement = new ScriptStatement(name="for", startPosition=pos, file=arguments.file, parent=parent);
257259
parent = currentStatement;
@@ -263,7 +265,7 @@ component extends="AbstractParser" {
263265
sb.append(mid(content, pos, 3));
264266
pos = pos+3;
265267
continue;
266-
} else if (lowerC == "w" && reFindNoCase("while\s*\(", content, pos) == pos) {
268+
} else if (lowerC == "w" && reFind("while\s*\(", lowerCaseContent, pos) == pos) {
267269
currentStatementStart = pos;
268270
currentStatement = new ScriptStatement(name="while", startPosition=pos, file=arguments.file, parent=parent);
269271
parent = currentStatement;
@@ -275,7 +277,7 @@ component extends="AbstractParser" {
275277
sb.append(mid(content, pos, 5));
276278
pos = pos+5;
277279
continue;
278-
} else if (lowerC == "d" && reFindNoCase("do\s*{", content, pos) == pos) {
280+
} else if (lowerC == "d" && reFind("do\s*{", lowerCaseContent, pos) == pos) {
279281
currentStatementStart = pos;
280282
currentStatement = new ScriptStatement(name="do", startPosition=pos, file=arguments.file, parent=parent);
281283
parent = currentStatement;
@@ -287,7 +289,7 @@ component extends="AbstractParser" {
287289
sb.append(mid(content, pos, 2));
288290
pos = pos+2;
289291
continue;
290-
} else if (lowerC == "t" && reFindNoCase("try\s*{", content, pos) == pos) {
292+
} else if (lowerC == "t" && reFind("try\s*{", lowerCaseContent, pos) == pos) {
291293
currentStatementStart = pos;
292294
currentStatement = new ScriptStatement(name="try", startPosition=pos, file=arguments.file, parent=parent);
293295
parent = currentStatement;
@@ -299,7 +301,7 @@ component extends="AbstractParser" {
299301
sb.append(mid(content, pos, 3));
300302
pos = pos+3;
301303
continue;
302-
} else if (lowerC == "c" && reFindNoCase("catch\s*\(", content, pos) == pos) {
304+
} else if (lowerC == "c" && reFind("catch\s*\(", lowerCaseContent, pos) == pos) {
303305
currentStatementStart = pos;
304306
currentStatement = new ScriptStatement(name="catch", startPosition=pos, file=arguments.file, parent=parent);
305307
parent = currentStatement;
@@ -311,7 +313,7 @@ component extends="AbstractParser" {
311313
sb.append(mid(content, pos, 5));
312314
pos = pos+5;
313315
continue;
314-
} else if (lowerC == "f" && reFindNoCase("finally\s*\{", content, pos) == pos) {
316+
} else if (lowerC == "f" && reFind("finally\s*\{", lowerCaseContent, pos) == pos) {
315317
currentStatementStart = pos;
316318
currentStatement = new ScriptStatement(name="finally", startPosition=pos, file=arguments.file, parent=parent);
317319
parent = currentStatement;
@@ -339,8 +341,33 @@ component extends="AbstractParser" {
339341
braceOpen = find("{", content, pos+1);
340342
semi = find(";", content, pos+1);
341343
paren = find("(", content, pos+1);
342-
quotePos = reFind("['""]", content, pos+1);
343-
temp = reFindNoCase("[^a-zA-Z0-9_.]*function[\t\r\n ]+[a-zA-Z_]", content, pos);
344+
345+
//quotePos = reFind("['""]", content, pos+1);
346+
quotePos = find("""", content, pos+1);
347+
temp = find("'", content, pos+1);
348+
if (temp < quotePos) {
349+
quotePos = temp;
350+
}
351+
temp = find("function", lowerCaseContent, pos);
352+
if (arguments.endPosition != 0) {
353+
//account for cfscript blocks
354+
if (temp > arguments.endPosition) {
355+
temp = 0;
356+
}
357+
if (braceOpen > arguments.endPosition) {
358+
braceOpen = 0;
359+
}
360+
if (semi > arguments.endPosition) {
361+
semi = 0;
362+
}
363+
if (quotePos > arguments.endPosition) {
364+
semi = 0;
365+
}
366+
}
367+
368+
if (temp != 0) {
369+
temp = reFind("[^a-zA-Z0-9_.]*function[\t\r\n ]+[a-zA-Z_]", lowerCaseContent, pos);
370+
}
344371

345372

346373
if (temp == 0) {

Tag.cfc

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,17 @@ component extends="Statement" {
4242
//custom tag assume true
4343
return true;
4444
}
45-
return listFindNoCase("cfoutput,cfmail,cfsavecontent,cfquery,cfdocument,cfpdf,cfhtmltopdf,cfhtmltopdfitem,cfscript,cfform,cfloop,cfif,cfelse,cfelseif,cftry,cfcatch,cffinally,cfstoredproc,cfswitch,cfcase,cfdefaultcase,cfcomponent,cffunction,cfchart,cfclient,cfdiv,cfdocumentitem,cfdocumentsection,cfformgroup,cfgrid,cfhttp,cfimap,cfinterface,cfinvoke,cflayout,cflock,cflogin,cfmap,cfmenu,cfmodule,cfpod,cfpresentation,cfthread,cfreport,cfsilent,cftable,cftextarea,cftimer,cftransaction,cftree,cfzip,cfwindow,cfxml", getName());
45+
if (getName() == "cfquery") {
46+
//if it has sql attribute, then no
47+
if (findNoCase("sql", getAttributeContent())) {
48+
if (structKeyExists(getAttributes(), "sql")) {
49+
return false;
50+
}
51+
}
52+
return true;
53+
}
54+
//removed cfquery because evaluated above
55+
return listFind("cfoutput,cfmail,cfsavecontent,cfdocument,cfpdf,cfhtmltopdf,cfhtmltopdfitem,cfscript,cfform,cfloop,cfif,cfelse,cfelseif,cftry,cfcatch,cffinally,cfstoredproc,cfswitch,cfcase,cfdefaultcase,cfcomponent,cffunction,cfchart,cfclient,cfdiv,cfdocumentitem,cfdocumentsection,cfformgroup,cfgrid,cfhttp,cfimap,cfinterface,cfinvoke,cflayout,cflock,cflogin,cfmap,cfmenu,cfmodule,cfpod,cfpresentation,cfthread,cfreport,cfsilent,cftable,cftextarea,cftimer,cftransaction,cftree,cfzip,cfwindow,cfxml", lcase(getName()));
4656
}
4757

4858
public string function getAttributeContent(stripTrailingSlash=false) {
@@ -89,7 +99,10 @@ component extends="Statement" {
8999
if (!hasInnerContent()) {
90100
return "";
91101
} else {
92-
return mid(getFile().getFileContent(), getStartTagEndPosition()+1, getEndTagStartPosition()-getStartTagEndPosition()-1);
102+
if (!structKeyExists(variables, "innerContent")) {
103+
variables.innerContent = mid(getFile().getFileContent(), getStartTagEndPosition()+1, getEndTagStartPosition()-getStartTagEndPosition()-1);
104+
}
105+
return variables.innerContent;
93106
}
94107
}
95108

@@ -223,11 +236,16 @@ component extends="Statement" {
223236
ArrayAppend(vars, attrs.variable);
224237
}
225238
break;
226-
case "cfparam":
239+
case "cfparam":
227240
if ( StructKeyExists(attrs, "name") ) {
228241
ArrayAppend(vars, attrs.name);
229242
}
230243
break;
244+
case "cfinvoke":
245+
if ( StructKeyExists(attrs, "returnvariable") ) {
246+
ArrayAppend(vars, attrs.returnvariable);
247+
}
248+
break;
231249
}
232250
return vars;
233251
}

TagParser.cfc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ component extends="AbstractParser" {
2626
if ( (c >= 97 && c<= 122) || c == 47 || c == 33 || (c >= 65 && c <= 90) ) {
2727
charPos = ltPos+1;
2828
} else {
29-
charPos = ReFind("[!/a-zA-Z]", content, ltPos+1);
29+
//charPos = ReFind("[!/a-zA-Z]", content, ltPos+1); the following is faster
30+
charPos = 0;
31+
for(i=ltPos+1;i<=contentLength;i++) {
32+
c = asc(mid(content, i, 1));
33+
if ( (c >= 97 && c<= 122) || c == 47 || c == 33 || (c >= 65 && c <= 90) ) {
34+
charPos = i;
35+
break;
36+
}
37+
}
3038
}
3139

3240
//spacePos = ReFind("[[:space:]]", content, ltPos+1); the following is faster...
@@ -112,7 +120,6 @@ component extends="AbstractParser" {
112120
//cfscript block
113121
local.scriptBlockFile = new ScriptParser();
114122
local.scriptBlockFile.parse(arguments.file, gtPos+1, endTagPos);
115-
116123
for (local.scriptStatement in local.scriptBlockFile.getStatements()) {
117124
if (!local.scriptStatement.hasParent()) {
118125
tag.addChild(local.scriptStatement);

box.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name":"cfmlparser",
3-
"version":"1.1.9",
3+
"version":"1.2.0",
44
"author":"Pete Freitag / Foundeo Inc.",
55
"location":"",
66
"directory":"",

tests/tests/TestTagParser.cfc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,45 @@ component extends="BaseTest" {
220220

221221
}
222222

223+
function testQueryWithSQLAttribute() {
224+
var parser = getParser("tag/query-with-sql-attr.cfm");
225+
var statements = parser.getStatements();
226+
var tag = "";
227+
var attr = "";
228+
229+
$assert.isGT(arrayLen(statements), 0);
230+
tag = statements[1];
231+
$assert.isTrue(tag.isComment());
232+
233+
$assert.isGT(arrayLen(statements), 0);
234+
tag = statements[2];
235+
$assert.isEqual("cfoutput", tag.getName());
236+
$assert.isTrue(tag.hasInnerContent());
237+
$assert.isTrue(find("test1", tag.getInnerContent()));
238+
239+
tag = statements[3];
240+
debug(tag.getVariables());
241+
$assert.isEqual("cfquery", tag.getName());
242+
attr = tag.getAttributes();
243+
//has sql attribute
244+
$assert.isTrue(structKeyExists(attr, "sql"));
245+
//has name attribute
246+
$assert.isTrue(structKeyExists(attr, "name"));
247+
//name=test1
248+
$assert.isEqual("test1", attr.name);
249+
//should not have innerContent
250+
$assert.isFalse(tag.hasInnerContent());
251+
$assert.isTrue(len(tag.getInnerContent()) == 0);
252+
253+
tag = statements[4];
254+
debug(tag.getVariables());
255+
$assert.isEqual("cfquery", tag.getName());
256+
attr = tag.getAttributes();
257+
$assert.isTrue(structKeyExists(attr, "name"));
258+
//name=test2
259+
260+
$assert.isEqual("test2", attr.name);
261+
}
262+
263+
223264
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<!--- a cfquery tag with a SQL attribute cannot have inner content --->
2+
<cfoutput encodefor="html">
3+
<cfquery name="test1" sql="SELECT 1">
4+
</cfoutput>
5+
<cfquery name="test2">SELECT 2</cfquery>

0 commit comments

Comments
 (0)