itext7 - equivalent method for getJavascript in pdfReader class in itext7 - javascript
Is there any equivalent method for getJavascript in pdf reader in itext7? we are looking for sanitizing the pdf document for malicious content using itext7.
As far as I can see there is not a dedicated method for that in iText 7.
Essentially, though, the old PdfReader.getJavaScript() method merely looked for the JavaScript name tree and put all the values into a string buffer.
You can output these values like this in iText 7:
PdfNameTree javascript = pdfDocument.getCatalog().getNameTree(PdfName.JavaScript);
Map<String, PdfObject> objs2 = javascript.getNames();
for (Map.Entry<String, PdfObject> entry : objs2.entrySet())
{
System.out.println();
System.out.println(entry.getKey());
System.out.println();
PdfObject object = entry.getValue();
if (object.isDictionary()) {
object = ((PdfDictionary)object).get(PdfName.JS);
if (object.isString()) {
System.out.println(((PdfString)object).getValue());
} else if (object.isStream()) {
System.out.println(new String(((PdfStream)object).getBytes()));
}
}
System.out.println();
}
(ShowDocumentLevelJavaScript test testREJECT_ContainsJavaScript)
Obviously you can in a similar manner collect the pieces of JavaScript into some string buffer.
In a comment James claimed
I tried using (and extending) your answer but cannot detect the JavaScript popup that fires when I open a sample PDF
Applying the above code to the PDF file provided by #James I get the output:
e.pdf Freeware Hinweis
if (app.viewerVersion>=5)
{
var result=app.alert(
"Diese Datei wurde mit der Freeware Version von CIB e.pdf erzeugt.\n\nMöchten Sie nähere Informationen?"
, 3
, 2
, "e.pdf Freeware Hinweis"
);
if (result==4)
getURL("http://www.cib.de/deutsch/products/pdfplugin/epdfbeta.asp", false);
}
The JavaScript popup can clearly be seen as an app.alert call here. Thus, I cannot reproduce the issue.
For anyone able to use iText 7.1.1 or newer, I expect the solution by mkl to be better. If you are forced to use iText 7.0.5 like I was, the following worked for my reference PDF:
private static boolean hasJavascript(PdfDocument pdfDoc, String theFile) {
int n = pdfDoc.getNumberOfPages();
for (int i = 1; i <= n; i++) {
PdfPage pdfPage = pdfDoc.getPage(i);
List<PdfAnnotation> annotList = pdfPage.getAnnotations();
if (ListUtility.hasData(annotList)) {
for (PdfAnnotation annot : annotList) {
if (annot.getSubtype().equals(PdfName.Link)) {
continue;
}
PdfDictionary annotationAction = annot.getPdfObject().getAsDictionary(PdfName.A);
if (annotationAction != null && PdfName.JavaScript.equals(annotationAction.get(PdfName.S))) {
PdfString javascript = annotationAction.getAsString(PdfName.JS);
if (StringUtility.hasData(javascript.getValue())) {
log.debug("JavaScript found in PDF on page " + i);
log.trace(javascript.getValue());
return true;
}
}
}
}
}
String javaScriptInPdf = getJavaScriptFromPdfDocument(pdfDoc);
if (StringUtility.hasData(javaScriptInPdf)) {
log.debug("JavaScript found using iText 7");
log.trace(javaScriptInPdf);
return true;
}
log.debug("JavaScript not found in PDF");
return false;
}
//
private static String getJavaScriptFromPdfDocument(PdfDocument pdfDoc) {
StringBuilder strBuf = new StringBuilder();
try {
PdfDictionary pdfDictionaryCatalog = pdfDoc.getCatalog().getPdfObject();
if (pdfDictionaryCatalog == null) {
log.trace("getJavaScriptFromPdfDocument(): pdfDictionaryCatalog null; return null");
return null;
}
PdfDictionary pdfDictionaryNames = pdfDictionaryCatalog.getAsDictionary(PdfName.Names);
if (pdfDictionaryNames == null) {
log.trace("getJavaScriptFromPdfDocument(): PdfDictionary for PdfName.Names null; return null");
return null;
}
PdfDictionary pdfDictionaryJavaScript = pdfDictionaryNames.getAsDictionary(PdfName.JavaScript);
if (pdfDictionaryJavaScript == null) {
log.trace("getJavaScriptFromPdfDocument(): PdfDictionary for PdfName.JavaScript null; return null");
return null;
}
Set<Entry<PdfName, PdfObject>> set = pdfDictionaryJavaScript.entrySet();
for (Entry<PdfName, PdfObject> pdfObjectEntry : set) {
PdfObject pdfObj = pdfObjectEntry.getValue();
if (pdfObj.isDictionary()) {
getJavaScriptFromPdfDictionary((PdfDictionary)pdfObj, strBuf);
} else if (pdfObj.isArray()) {
getJavaScriptFromPdfArray((PdfArray)pdfObj, strBuf);
} else if (pdfObj.isString() && pdfObjectEntry.getKey().getValue().equals(PdfName.JS.getValue())) {
strBuf.append(((PdfString) pdfObj).getValue());
}
}
}
catch (Exception e) {
log.debug(e,e);
}
return strBuf.toString();
}
//
private static void getJavaScriptFromPdfArray(PdfArray pdfArray, StringBuilder strBuf) {
if (pdfArray == null) {
return;
}
for (PdfObject pdfObj : pdfArray) {
// To get same output as getJavaScriptUsingiText559(), not appending String values found in array to strBuf
if (pdfObj == null) {
continue;
}
else if (pdfObj.isDictionary()) {
getJavaScriptFromPdfDictionary((PdfDictionary)pdfObj, strBuf);
}
else if (pdfObj.isArray()) {
getJavaScriptFromPdfArray((PdfArray)pdfObj, strBuf);
}
}
}
//
private static void getJavaScriptFromPdfDictionary(PdfDictionary pdfDict, StringBuilder strBuf) {
if (pdfDict == null) {
return;
}
PdfObject pdfObj = pdfDict.get(PdfName.JS);
if (pdfObj == null) {
return;
}
if (pdfObj.isString()) {
strBuf.append(((PdfString) pdfObj).getValue());
}
else if (pdfObj.isStream()) {
strBuf.append(getStringFromPdfStream((PdfStream) pdfObj, TRUNCATE_PDF_STREAM_AT));
}
else if (pdfObj.isDictionary()) {
getJavaScriptFromPdfDictionary((PdfDictionary) pdfObj, strBuf);
}
else if (pdfObj.isArray()) {
getJavaScriptFromPdfArray((PdfArray)pdfObj, strBuf);
}
}
I know this is a very old thread, still sharing my code to add couple of things, on top of what mkl and James wrote.
// iText V 7.0.2
//To get javascript that is added through OpenAction
PdfDocument srcPdf = new PdfDocument(new PdfReader(srcFilePath));
PdfDictionary pdfDictionaryCatalog = srcPdf.getCatalog().getPdfObject();
PdfDictionary namesDictionary = pdfDictionaryCatalog.getAsDictionary(PdfName.OpenAction);
if(namesDictionary != null) {
PdfObject pdfObj = namesDictionary.get(PdfName.JS);
if(pdfObj != null) {
StringBuilder strBuf = new StringBuilder();
if (pdfObj.isDictionary()) {
getJavaScriptFromPdfDictionary((PdfDictionary)pdfObj, strBuf);
}else if (pdfObj.isArray()) {
getJavaScriptFromPdfArray((PdfArray)pdfObj, strBuf);
} else if (pdfObj.isString()) {
strBuf.append(((PdfString) pdfObj).getValue());
}
System.out.println("*****OPENACTION****** "+strBuf.toString());
}
}
// To get java script available from NAMES dictionary
namesDictionary = pdfDictionaryCatalog.getAsDictionary(PdfName.Names);
if(namesDictionary != null) {
PdfDictionary javascriptDictionary = namesDictionary.getAsDictionary(PdfName.JavaScript);
if(javascriptDictionary != null) {
StringBuilder strBuf = new StringBuilder();
Set<Entry<PdfName, PdfObject>> set = javascriptDictionary.entrySet();
for (Entry<PdfName, PdfObject> entry : set) {
PdfObject pdfObj = entry.getValue();
if (pdfObj.isDictionary()) {
getJavaScriptFromPdfDictionary((PdfDictionary)pdfObj, strBuf);
}else if (pdfObj.isArray()) {
getJavaScriptFromPdfArray((PdfArray)pdfObj, strBuf);
} else if (pdfObj.isString() && entry.getKey().getValue().equals(PdfName.JS.getValue())) {
strBuf.append(((PdfString) pdfObj).getValue());
}
}
System.out.println("*****JAVASCRIPT****** "+strBuf.toString());
}
}
// To get java script from name tree JAVASCRIPT
PdfNameTree nameTree = srcPdf.getCatalog().getNameTree(PdfName.JavaScript);
if(nameTree != null) {
Map<String, PdfObject> objs = nameTree.getNames();
if(objs != null) {
StringBuilder strBuf = new StringBuilder();
for (Entry<String, PdfObject> entry : objs.entrySet()) {
PdfObject pdfObj = entry.getValue();
if (pdfObj.isDictionary()) {
getJavaScriptFromPdfDictionary((PdfDictionary)pdfObj, strBuf);
}else if (pdfObj.isArray()) {
getJavaScriptFromPdfArray((PdfArray)pdfObj, strBuf);
} else if (pdfObj.isString() && entry.getKey().equals(PdfName.JS.getValue())) {
strBuf.append(((PdfString) pdfObj).getValue());
}
}
System.out.println("*****JAVASCRIPT NAMED TREE****** "+strBuf.toString());
}
}
// To get java script at the annotation action level
for (int i = 1; i <= srcPdf.getNumberOfPages(); i++) {
PdfPage page = srcPdf.getPage(i);
List<PdfAnnotation> annotList = page.getAnnotations();
if(annotList != null) {
for (PdfAnnotation pdfAnnotation : annotList) {
if(pdfAnnotation.getPdfObject() != null) {
PdfDictionary annotationAction = pdfAnnotation.getPdfObject().getAsDictionary(PdfName.A);
if (annotationAction != null && PdfName.JavaScript.equals(annotationAction.get(PdfName.S))) {
PdfString javascript = annotationAction.getAsString(PdfName.JS);
if(javascript != null) {
System.out.println("ANNOTATION "+javascript);
}
}
}
}
}
}
/*getJavaScriptFromPdfDictionary() and getJavaScriptFromPdfArray() methods are the same from James answer. */
Related
Issue with JSON in UWP WebView using C# to JavaScript not being read properly
This is rather perplexing. I'm converting an HTML5 game to UWP by using a WebView to host the game content and would like to back up from localStorage in the WebView and create a duplicate in the localStorage for the app as a whole (that is, from browser storage to package storage). This is of course an intended safety net in case the player craps out on hardware (as I obviously want the data to back up to the player's cloud storage) however something's not quite right. Specifically, the backups are written properly but when they're read back from the host app into the HTML5 portion using Javascript methods connected to the host through a WinRT component they're not having an effect on the localStorage in the WebView (which in turn makes it look like there's no save data, so the continue option is disabled). Here's a code sample for what I'm doing: var i; for (i = -1; i <= 200; i++) { var data = window.UWPConnect.getSaveFile(i); var key = 'File'+i; if (i === -1) key = 'Config'; if (i === 0) key = 'Global'; localStorage.setItem(key, data); } This first portion reads my WinRT component to load save data from the disk. public void doSave(int key, string data) { /*StorageFolder folder = ApplicationData.Current.LocalFolder; StorageFile saveWrite = await folder.CreateFileAsync("saveData" + key + ".json", CreationCollisionOption.ReplaceExisting); try { await saveWrite.DeleteAsync(); } catch { } String[] lines = { data }; await FileIO.WriteLinesAsync(saveWrite, lines);*/ if (key == -1) { System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\config.json", data); } else if (key == 0) { System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\global.json", data); } else { System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\saveData" + key + ".json", data); } } public void doStartup() { StorageFolder folder = ApplicationData.Current.LocalFolder; for (int i = -1; i <= 200; i++) { try { if (i == -1) { saveData[i] = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\config.json"); } else if (i == 0) { saveData[i] = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\global.json"); } else { saveData[i] = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\saveData" + i + ".json"); } Debug.WriteLine(saveData[i]); } catch {} /*Debug.WriteLine(File.Exists(SaveFile)); if (File.Exists(SaveFile)) { try { saveData[i] = File.ReadAllText(SaveFile); Debug.WriteLine(saveData[i]); } catch { } }*/ } } public string getSaveFile(int savefileId) { string data; try { data = saveData[savefileId]; if (data == null) data = ""; Debug.WriteLine(data); } catch { data = ""; } return data; } And this second portion handles the saving and loading from disk (which is taken from the WinRT component).
I think I figured it out. In the data reader where it loads and saves I had to use a different set of instructions on the WinRT side, so here's the new code structure: public void doSave(int key, string data) { if (key == -1) { System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\config.json", data); } else if (key == 0) { System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\global.json", data); } else { System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\file" + key + ".json", data); } } public void doBackup(int key, string data) { System.IO.File.WriteAllText(ApplicationData.Current.LocalFolder.Path + "\\backup" + key + ".json", data); } public void doStartup() { for (int i = 0; i <= 200; i++) { if (i == -1) { if (System.IO.File.Exists(ApplicationData.Current.LocalFolder.Path + "\\config.json")) { saveData[i] = System.IO.File.ReadAllText(ApplicationData.Current.RoamingFolder.Path + "\\config.json"); } } else if (i == 0) { if (System.IO.File.Exists(ApplicationData.Current.LocalFolder.Path + "\\global.json")) { saveData[i] = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\global.json"); } } else { if (System.IO.File.Exists(ApplicationData.Current.LocalFolder.Path + "\\file" + i + ".json")) { saveData[i] = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\file" + i + ".json"); } if (System.IO.File.Exists(ApplicationData.Current.LocalFolder.Path + "\\backup" + i + ".json")) { backup[i] = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\backup" + i + ".json"); } } } } public string getSaveFile(int savefileId) { string data; try { data = saveData[savefileId]; if (data == null) data = ""; Debug.WriteLine(data); } catch { data = ""; } return data; } public string getBackup(int savefileId) { string data; try { data = backup[savefileId]; if (data == null) data = ""; Debug.WriteLine(data); } catch { data = ""; } return data; } public string getConfig() { string data; try { data = System.IO.File.ReadAllText(ApplicationData.Current.LocalFolder.Path + "\\config.json"); if (data == null) data = ""; Debug.WriteLine(data); } catch { data = ""; } return data; }
JavaScript GetLocalResourceObject from .resx file
I'm trying to get the local ressources key from .rsex file in javascript, but it doesn't work, I got error "not available". Thanks for your help. var key = "errMesgLength" var val = eval('<%= GetLocalResourceObject("' + key + '") %>'); lblMessage.innerHTML = val;
Thank you Michael for help. I have found a solution. In C# code I have used a web method [WebMethod()] public static string GetLocalRessources(string key) { var currentLanguage = (Language)HttpContext.Current.Session["CurrentLanguage"]; if (currentLanguage == Language.French) { Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("fr-FR"); } else { Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US"); } return HttpContext.GetLocalResourceObject("~/SelfRegistration.aspx", key, Thread.CurrentThread.CurrentCulture).ToString(); } and in JS code, I have used PageMethos call function IsIMEI() { var imei = document.querySelector(".txt-imei"); var lblMessage = document.querySelector(".lblMsgError"); var key; if (imei) { if (imei.value !== "IMEI / Serial Number") { if (imei.value.toString().length > 7) { imei.style.border = ""; lblMessage.innerHTML = ""; return true; } else { key = "errMesgLength" PageMethods.GetLocalRessources(key, onSucess, onError); return false; } } else { imei.style.border = "1px solid red"; key = "errMesgIMEI" PageMethods.GetLocalRessources(key, onSucess, onError); return false; } } } function onSucess(result) { var lblMessage = document.querySelector(".lblMsgError"); lblMessage.innerHTML = result; } function onError(error) { alert(error._message); }
Creating cookies in javascript and use it into mvc 4 action result
I have make cookies in java script page but when used in the Controller page it shows null and i Check in the browser cookies is also created so please help me for this ...... <script> #Html.Raw(ViewBag.CallJSFuncOnPageLoad) function IPDetction() { $.getJSON("http://ip-api.com/json/?callback=?", function (data) { var items = []; $.each(data, function (key, val) { if (key == 'city') { document.cookie = "DetectedCityName=" + val; } if (key == 'region') { document.cookie = "DetectedRegionName=" + val; } }); }); } </script> #{ string DetectedCityName = "Toronto"; try { RateMadnessfinal.DataModel.RateMadnessdbEntities db = new RateMadnessfinal.DataModel.RateMadnessdbEntities(); DetectedCityName = HttpContext.Current.Request.Cookies["DetectedCityName"].Value; var getCityID = db.Macities.Where(c => c.CityName.Contains(DetectedCityName)).ToList(); if ((getCityID != null) && (getCityID.Count > 0)) { DetectedCityName = getCityID.FirstOrDefault().CityName; } else { DetectedCityName = "Toronto"; } } catch (Exception e) { DetectedCityName = "Toronto"; } }
Execute JavaScript code and get the object and/or array result from Xilium.CefGlue
I am using CefGlue to execute JavaScript code against a browser. if (browser.GetMainFrame().V8Context.TryEval(script, out returnValue, out ex)) { if (returnValue != null) { SetClientValuetFromV8Value(returnValue, result); } } And that is the method responsible for maping the CefV8Value to .NET data types private static void SetClientValuetFromV8Value(CefV8Value returnValue, JSResult result) { if (returnValue == null) { throw new ArgumentNullException("returnValue"); } if (result == null) { throw new ArgumentNullException("result"); } object v = null; string type; if (returnValue.IsString) { v = returnValue.GetStringValue(); } else if (returnValue.IsInt) { v = returnValue.GetIntValue(); } else if (returnValue.IsBool) { v = returnValue.GetBoolValue(); } else if (returnValue.IsDate) { v = returnValue.GetDateValue(); } else if (returnValue.IsDouble) { v = returnValue.GetDoubleValue(); } else if (returnValue.IsArray) { // what do we put in here?? } else if (returnValue.IsObject) { // what do we put in here?? } result.JsValue = new JSValue(v); } It seems to me that there is no way to get the value when IsObject or IsArray return true. There is no method available for GetObjectValue() or GetArrayValues() I know that I can make my javascript code convert everything to string before returning it but that is not what I need.
In-browser JavaScript code editor with syntax highlighting support for Smarty template tags?
I have searched high and low on the Interwebs, and found some really awesome JS code editors with syntax highlighting and indentation and more... but none seem to have support for Smarty template tags yet. A new Smarty mode for CodeMirror would be the best, but I'll use a different editor if I need to. I did find this blog post... but it is VERY simple, and I would like to still support mixed HTML/CSS/JS highlighting, like the PHP mode for CodeMirror. I just thought I would check with the SO hive mind before embarking on rolling my own CodeMirror mode. If I do make a new mode (and get anywhere with it) I'll post it here. Thanks!
I made some tries to get a mixed mode with smarty and although my work is not perfect, so far it works well enough for me. I started from de htmlmixedmode to add a smarty mode : CodeMirror.defineMode("smartymixed", function(config, parserConfig) { var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true}); var smartyMode = CodeMirror.getMode(config, "smarty"); var jsMode = CodeMirror.getMode(config, "javascript"); var cssMode = CodeMirror.getMode(config, "css"); function html(stream, state) { var style = htmlMode.token(stream, state.htmlState); if (style == "tag" && stream.current() == ">" && state.htmlState.context) { if (/^script$/i.test(state.htmlState.context.tagName)) { state.token = javascript; state.localState = jsMode.startState(htmlMode.indent(state.htmlState, "")); state.mode = "javascript"; } else if (/^style$/i.test(state.htmlState.context.tagName)) { state.token = css; state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); state.mode = "css"; } } return style; } function maybeBackup(stream, pat, style) { var cur = stream.current(); var close = cur.search(pat); if (close > -1) stream.backUp(cur.length - close); return style; } function javascript(stream, state) { if (stream.match(/^<\/\s*script\s*>/i, false)) { state.token = html; state.localState = null; state.mode = "html"; return html(stream, state); } return maybeBackup(stream, /<\/\s*script\s*>/, jsMode.token(stream, state.localState)); } function css(stream, state) { if (stream.match(/^<\/\s*style\s*>/i, false)) { state.token = html; state.localState = null; state.mode = "html"; return html(stream, state); } return maybeBackup(stream, /<\/\s*style\s*>/, cssMode.token(stream, state.localState)); } function smarty(stream, state) { style = smartyMode.token(stream, state.localState); if ( state.localState.tokenize == null ) { // back to anything from smarty state.token = state.htmlState.tokens.pop(); state.mode = state.htmlState.modes.pop(); state.localState = state.htmlState.states.pop(); // state.htmlState; } return(style); } return { startState: function() { var state = htmlMode.startState(); state.modes = []; state.tokens = []; state.states = []; return {token: html, localState: null, mode: "html", htmlState: state}; }, copyState: function(state) { if (state.localState) var local = CodeMirror.copyState( ( state.token == css ) ? cssMode : (( state.token == javascript ) ? jsMode : smartyMode ), state.localState); return {token: state.token, localState: local, mode: state.mode, htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; }, token: function(stream, state) { if ( stream.match(/^{[^ ]{1}/,false) ) { // leaving anything to smarty state.htmlState.states.push(state.localState); state.htmlState.tokens.push(state.token); state.htmlState.modes.push(state.mode); state.token = smarty; state.localState = smartyMode.startState(); state.mode = "smarty"; } return state.token(stream, state); }, compareStates: function(a, b) { if (a.mode != b.mode) return false; if (a.localState) return CodeMirror.Pass; return htmlMode.compareStates(a.htmlState, b.htmlState); }, electricChars: "/{}:" } }, "xml", "javascript", "css", "smarty"); CodeMirror.defineMIME("text/html", "smartymixed"); The switch to smarty mode is made in token function only but ... You also have to patch the other basic modes ( css , javascript & xml ) to stop them on the { character so you can fall back in the token function to test it against a regexp ( { followed by a non blank character ).
This may help. I wrote a Smarty mode for CodeMirror2 this weekend. See: http://www.benjaminkeen.com/misc/CodeMirror2/mode/smarty/ I've also forked the CodeMirror project with my change here: https://github.com/benkeen/CodeMirror2 All the best - Ben [EDIT: this is now part of the main script. I'll be shortly adding a Smarty/HTML/CSS/JS mode].
The second part of the answer : a patch in benjamin smarty file to be able to leave it and fall back in smartymixedmode. So here is the patched verson of mode/smarty/smarty.js CodeMirror.defineMode("smarty", function(config, parserConfig) { var breakOnSmarty = ( config.mode == "smartymixed" ) ? true : false; // we are called in a "smartymixed" context var keyFuncs = ["debug", "extends", "function", "include", "literal"]; var last; var regs = { operatorChars: /[+\-*&%=<>!?]/, validIdentifier: /[a-zA-Z0-9\_]/, stringChar: /[\'\"]/ } var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{"; var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}"; function ret(style, lst) { last = lst; return style; } function tokenizer(stream, state) { function chain(parser) { state.tokenize = parser; return parser(stream, state); } if (stream.match(leftDelim, true)) { if (stream.eat("*")) { return chain(inBlock("comment", "*" + rightDelim)); } else { state.tokenize = inSmarty; return ( breakOnSmarty == true ) ? "bracket" : "tag"; } } else { // I'd like to do an eatWhile() here, but I can't get it to eat only up to the rightDelim string/char stream.next(); return null; } } function inSmarty(stream, state) { if (stream.match(rightDelim, true)) { state.tokenize = ( breakOnSmarty ) ? null : tokenizer; return ( breakOnSmarty == true ) ? ret("bracket", null) : ret("tag", null); } var ch = stream.next(); if (ch == "$") { stream.eatWhile(regs.validIdentifier); return ret("variable-2", "variable"); } else if (ch == ".") { return ret("operator", "property"); } else if (regs.stringChar.test(ch)) { state.tokenize = inAttribute(ch); return ret("string", "string"); } else if (regs.operatorChars.test(ch)) { stream.eatWhile(regs.operatorChars); return ret("operator", "operator"); } else if (ch == "[" || ch == "]") { return ret("bracket", "bracket"); } else if (/\d/.test(ch)) { stream.eatWhile(/\d/); return ret("number", "number"); } else { if (state.last == "variable") { if (ch == "#") { stream.eatWhile(regs.validIdentifier); return ret("property", "property"); } else if (ch == "|") { stream.eatWhile(regs.validIdentifier); return ret("qualifier", "modifier"); } } else if (state.last == "whitespace") { stream.eatWhile(regs.validIdentifier); return ret("attribute", "modifier"); } else if (state.last == "property") { stream.eatWhile(regs.validIdentifier); return ret("property", null); } else if (/\s/.test(ch)) { last = "whitespace"; return null; } var str = ""; if (ch != "/") { str += ch; } var c = ""; while ((c = stream.eat(regs.validIdentifier))) { str += c; } var i, j; for (i=0, j=keyFuncs.length; i<j; i++) { if (keyFuncs[i] == str) { return ret("keyword", "keyword"); } } if (/\s/.test(ch)) { return null; } return ret("tag", "tag"); } } function inAttribute(quote) { return function(stream, state) { while (!stream.eol()) { if (stream.next() == quote) { state.tokenize = inSmarty; break; } } return "string"; }; } function inBlock(style, terminator) { return function(stream, state) { while (!stream.eol()) { if (stream.match(terminator)) { state.tokenize = ( breakOnSmarty == true ) ? null : tokenizer; break; } stream.next(); } return style; }; } return { startState: function() { return { tokenize: tokenizer, mode: "smarty", last: null }; }, token: function(stream, state) { var style = state.tokenize(stream, state); state.last = last; return style; }, electricChars: "" } }); CodeMirror.defineMIME("text/x-smarty", "smarty"); The 1st line check if we are called by the smartymixed mode and tests are made on this contition, allowing smarty mode to run as before.