PageRenderTime 34ms CodeModel.GetById 18ms app.highlight 11ms RepoModel.GetById 1ms app.codeStats 0ms

/IronPython_Main/Hosts/Silverlight/Microsoft.Scripting.SilverLight/DynamicScriptTags.cs

#
C# | 399 lines | 249 code | 50 blank | 100 comment | 96 complexity | 8b589024127f6ddb5ba97b987fcea712 MD5 | raw file
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Apache License, Version 2.0, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Apache License, Version 2.0.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15
 16using System;
 17using System.Collections.Generic;
 18using System.Text;
 19using System.Windows.Browser;
 20using System.IO;
 21using Microsoft.Scripting.Hosting;
 22using Microsoft.Scripting.Utils;
 23
 24namespace Microsoft.Scripting.Silverlight {
 25
 26    /// <summary>
 27    /// Manages script tags that hold DLR-based code.
 28    /// </summary>
 29    public class DynamicScriptTags {
 30
 31        /// <summary>
 32        /// Inline strings and external URIs for script-tag registered 
 33        /// to this control.
 34        /// </summary>
 35        public class ScriptCode {
 36            public string Language { get; private set; }
 37            public bool Defer { get; private set; }
 38            public string Inline { get; set; }
 39            public Uri External { get; set; }
 40
 41            public ScriptCode(string lang, bool defer) {
 42                Language = lang;
 43                Defer = defer;
 44            }
 45        }
 46
 47        /// <summary>
 48        /// Script code registered for this Silverlight control
 49        /// </summary>
 50        public List<ScriptCode> Code { get; private set; }
 51
 52        public List<Uri> ZipPackages { get; private set; }
 53
 54        /// <summary>
 55        /// Holds onto the language config
 56        /// </summary>
 57        private DynamicLanguageConfig _LangConfig;
 58
 59        /// <summary>
 60        /// true while the script-tag is running, false otherwise
 61        /// </summary>
 62        internal bool RunningScriptTags;
 63
 64        public DynamicScriptTags(DynamicLanguageConfig langConfig) {
 65            RunningScriptTags = false;
 66            _LangConfig = langConfig;
 67            Code = new List<ScriptCode>();
 68            ZipPackages = new List<Uri>();
 69        }
 70
 71        public void DownloadHtmlPage(Action onComplete) {
 72            if (!HtmlPage.IsEnabled) {
 73                onComplete();
 74            } else {
 75                DownloadAndCache(new List<Uri>() { DynamicApplication.HtmlPageUri }, onComplete);
 76            }
 77        }
 78
 79        /// <summary>
 80        /// Scrapes the HTML page and populates the "Code" structure.
 81        /// </summary>
 82        public void FetchScriptTags() {
 83            if (!HtmlPage.IsEnabled)
 84                return;
 85
 86            var scriptTags = HtmlPage.Document.GetElementsByTagName("script");
 87
 88            foreach (ScriptObject scriptTag in scriptTags) {
 89                var e = (HtmlElement)scriptTag;
 90                var type = (string)e.GetAttribute("type");
 91                var src = (string)e.GetAttribute("src");
 92
 93                string language = null;
 94
 95                // Find the language by either mime-type or script's file extension
 96                if (type != null)
 97                    language = GetLanguageByType(type);
 98                else if (src != null)
 99                    language = GetLanguageByExtension(Path.GetExtension(src));
100
101                // Only move on if the language was found
102                if (language != null) {
103
104                    var initParams = DynamicApplication.Current.InitParams;
105
106                    // Process this script-tag if ...
107                    if (
108                        // it's class is "*" ... OR
109                        (e.CssClass == "*") ||
110
111                        // the xamlid initparam is set and matches this tag's class ... OR
112                        (initParams.ContainsKey("xamlid") && initParams["xamlid"] != null &&
113                         e.CssClass != null && e.CssClass == initParams["xamlid"]) ||
114
115                        // the xamlid initparam is not set and this tag does not have a class
116                        (!initParams.ContainsKey("xamlid") && (e.CssClass == null || e.CssClass.Length == 0))
117                    ) {
118                        bool defer = (bool)e.GetProperty("defer");
119
120                        _LangConfig.LanguagesUsed[language] = true;
121
122                        var sc = new ScriptCode(language, defer);
123
124                        if (src != null) {
125                            sc.External = DynamicApplication.MakeUri(src);
126                        } else {
127
128                            var innerHtml = (string)e.GetProperty("innerHTML");
129                            if (innerHtml != null) {
130                                // IE BUG: inline script-tags have an extra newline at the front,
131                                // so remove it ...
132                                if (HtmlPage.BrowserInformation.Name == "Microsoft Internet Explorer" && innerHtml.IndexOf("\r\n") == 0) {
133                                    innerHtml = innerHtml.Substring(2);
134                                }
135
136                                sc.Inline = innerHtml;
137                            }
138                        }
139
140                        Code.Add(sc);
141                    }
142
143                // Lastly, check to see if this is a zip file
144                } else if (src != null && ((type != null && type == "application/x-zip-compressed") || Path.GetExtension(src) == ".zip")) {
145                    
146                    ZipPackages.Add(DynamicApplication.MakeUri(src));
147
148                }
149            }
150        }
151
152        /// <summary>
153        /// Gathers the set of external script URIs, and asks the 
154        /// HttpVirtualFilesystem to download and cache the URIs. Calls the
155        /// delegate when all downloads are complete.
156        /// </summary>
157        public void DownloadExternalCode(Action onComplete) {
158            var externalUris = new List<Uri>();
159            foreach (var sc in Code)
160                if (sc.External != null)
161                    externalUris.Add(sc.External);
162            foreach (var zip in ZipPackages)
163                externalUris.Add(zip);
164            DownloadAndCache(externalUris, onComplete);
165        }
166
167        private void DownloadAndCache(List<Uri> uris, Action onComplete) {
168            if (!HtmlPage.IsEnabled) {
169                onComplete();
170            } else {
171                ((HttpVirtualFilesystem)HttpPAL.PAL.VirtualFilesystem).
172                    DownloadAndCache(uris, onComplete);
173            }
174        }
175
176        /// <summary>
177        /// Are there any registered script tags?
178        /// </summary>
179        public bool HasScriptTags {
180            get {
181                foreach (var i in Code)
182                    if (i.External != null && i.Inline != null)
183                        return true;
184                return false;
185            }
186        }
187
188        /// <summary>
189        /// Runs the registered script tags against the DynamicEngine.
190        /// </summary>
191        public void Run(DynamicEngine engine) {
192            var inlineScope = engine.CreateScope();
193            foreach (var sc in Code) {
194                engine.Engine = _LangConfig.GetEngine(sc.Language);
195                if (engine.Engine != null && !sc.Defer) {
196                    ScriptSource sourceCode = null;
197                    ScriptScope scope = null;
198                    if(sc.External != null) {
199                        var code = BrowserPAL.PAL.VirtualFilesystem.GetFileContents(sc.External);
200                        if (code == null) continue;
201                        sourceCode =
202                            engine.Engine.CreateScriptSourceFromString(
203                                code,
204                                DynamicApplication.BaseUri.MakeRelativeUri(sc.External).ToString(),
205                                SourceCodeKind.File
206                            );
207                        scope = engine.CreateScope();
208                    } else if (sc.Inline != null) {
209                        Assert.NotNull(DynamicApplication.HtmlPageUri);
210                        var page = BrowserPAL.PAL.VirtualFilesystem.GetFileContents(DynamicApplication.HtmlPageUri);
211                        var code = AlignSourceLines(sc.Inline, page);
212                        sourceCode =
213                            engine.Engine.CreateScriptSourceFromString(
214                                RemoveMargin(code),
215                                DynamicApplication.HtmlPageUri.ToString(),
216                                SourceCodeKind.File
217                            );
218                        scope = inlineScope;
219                    }
220                    if (sourceCode != null && scope != null) {
221                        RunningScriptTags = true;
222                        sourceCode.Compile(new ErrorFormatter.Sink()).Execute(scope);
223                        RunningScriptTags = false;
224                    }
225                }
226            }
227        }
228
229        private string AlignSourceLines(string partialSource, string fullSource) {
230            partialSource = partialSource.Replace("\r", "");
231            fullSource = fullSource.Replace("\r", "");
232            StringBuilder final = new StringBuilder();
233
234            if (fullSource.Contains(partialSource)) {
235                int offset = fullSource.IndexOf(partialSource);
236                int partialOffset = offset + partialSource.Length;
237                for (int i = 0; i < fullSource.Length; i++) {
238                    if ((i < offset || i >= partialOffset) ) {
239                        if (fullSource[i] == '\n') {
240                            final.Append('\n');
241                        }
242                    } else {
243                        final.Append(fullSource[i]);
244                    }
245                }
246            }
247
248            return final.ToString();
249        }
250
251        /// <summary>
252        /// Removes as much of a margin as possible from "text".
253        /// </summary>
254        public static string RemoveMargin(string text) {
255            return RemoveMargin(text, -1, true, true);
256        }
257
258        /// <summary>
259        /// Removes as much of a margin as possible from "text". 
260        /// </summary>
261        /// <param name="text">"text" to remove margin from</param>
262        /// <param name="firstLineMargin">What is the first line's margin. Set to "-1" to assume no margin.</param>
263        /// <param name="firstLine">does the "firstLine" need to be processed?</param>
264        /// <param name="keepLines">Should lines be kept (true) or removed (false)</param>
265        /// <returns>the de-margined text</returns>
266        private static string RemoveMargin(string text, int firstLineMargin, bool firstLine, bool keepLines) {
267            var reader = new StringReader(text);
268            var writer = new StringWriter();
269            string line;
270            var processedWriter = new StringWriter();
271            while ((line = reader.ReadLine()) != null) {
272                if (firstLine) {
273                    // skips all blank lines at the beginning of the string
274                    if (line.Length == 0) {
275                        if (keepLines) {
276                            writer.Write("\n");
277                            processedWriter.Write("\n");
278                        }
279                        continue;
280                    }
281
282                    // get the first real line's margin as a reference
283                    if(firstLineMargin == -1)
284                        firstLineMargin = GetMarginSize(line);
285                    firstLine = false;
286                }
287
288                if (line.Trim().Length != 0) {
289                    // if any line does not have any margin spaces to
290                    // remove, stop removing margins and reprocess previously
291                    // processed lines without removing margins as well; if any
292                    // line's margins are less than , then there is no
293                    // margin.
294                    var currentLineMargin = GetMarginSize(line);
295                    if (currentLineMargin < firstLineMargin) {
296                        var processedText = RemoveMargin(processedWriter.ToString(), currentLineMargin, true, keepLines);
297                        writer.Close();
298                        writer = null;
299                        writer = new StringWriter(new StringBuilder(processedText));
300                        firstLineMargin = currentLineMargin;
301                    }
302                } else if (reader.Peek() == -1) {
303                    // skip if it's the last line
304                    continue;
305                }
306
307                var newLine = line;
308                newLine = RemoveSpacesFromStart(firstLineMargin, line);
309
310                writer.Write(newLine + "\n");
311                processedWriter.Write(line + "\n");
312            }
313
314            string result = writer.ToString();
315            reader.Close();
316            writer.Close();
317            return result;
318        }
319
320        /// <summary>
321        /// returns the number of spaces in the beginning of "line"
322        /// </summary>
323        /// <param name="line">a string to find the number of spaces in</param>
324        /// <returns>the number of spaces at the beginning of "line"</returns>
325        private static int GetMarginSize(string line) {
326            var count = 0;
327            foreach(char c in line) {
328                if(c == ' ') {
329                    count++;
330                } else {
331                    return count;
332                }
333            }
334            return count;
335        }
336
337        /// <summary>
338        /// Removes "n" spaces from the start of "line". If not all those chars
339        /// spaces, then "line" is returned in it's entirety.
340        /// </summary>
341        /// <param name="n">Number of spaces to remove from "line"</param>
342        /// <param name="line">The string to remove spaces from</param>
343        /// <returns>
344        /// A string with "n" spaces removed, or a copy of "line" if there exists
345        /// a non-space character in the first "n" spaces.
346        /// </returns>
347        private static string RemoveSpacesFromStart(int n, string line) {
348            n = line.Length < n ? line.Length : n;
349            for (int i = 0; i < n; i++)
350                if (line[i] != ' ')
351                    return line;
352            return line.Remove(0, n);
353        }
354
355        /// <summary>
356        /// Get the language by name; if it exists get back the "main"
357        /// language name, otherwise null
358        /// </summary>
359        public string GetLanguageByType(string mimeType) {
360            return GetLanguage(GetLanguageNameFromType(mimeType), (dli) => { return dli.Names; });
361        }
362
363        /// <summary>
364        /// Get the language by file extension; if it exists get back the 
365        /// "main" language name, otherwise null
366        /// </summary>
367        /// <param name="extension"></param>
368        /// <returns></returns>
369        public string GetLanguageByExtension(string extension) {
370            return GetLanguage(extension, (dli) => { return dli.Extensions; });
371        }
372
373        private string GetLanguage(string token, Func<DynamicLanguageInfo, string[]> getProperty) {
374            if (token == null) return null;
375            foreach (var l in _LangConfig.Languages) {
376                foreach (var n in getProperty(l)) {
377                    if (n.ToLower() == token.ToLower()) {
378                        return (l.Names.Length > 0 ? l.Names[0] : token).ToLower();
379                    }
380                }
381            }
382            return null;
383        }
384
385        /// <summary>
386        /// Given a mime-type, return the language name
387        /// </summary>
388        public string GetLanguageNameFromType(string type) {
389            if (!type.StartsWith("application/x-") && 
390                !type.StartsWith("text/") && 
391                !type.StartsWith("application/")) return null;
392            string lang = type.Substring(type.LastIndexOf('/') + 1);
393            if (lang.StartsWith("x-")) {
394                lang = lang.Substring(2);
395            }
396            return lang;
397        }
398    }
399}