예제 #1
0
        /// <summary>
        /// Gets the relative path of the template to use with the current file taking into account all the possible parameters/fields that control this setting
        /// </summary>
        /// <returns></returns>
        private static string GetCurrentTemplateFile(MarkdownFile md)
        {
            //Get the template name that is going to be used (Front Matter or configuration), if any.
            string templateName = md.TemplateName;

            if (string.IsNullOrEmpty(templateName) || templateName.ToLowerInvariant() == "none")
            {
                return(string.Empty);    //Use the default basic HTML5 template
            }
            if (templateName.ToLowerInvariant() == "raw")
            {
                return("raw");   //Use raw contents, without any wrapping HTML tags
            }
            //The name (or sub-path) for the layout file (.html normaly) to be used
            string layoutName = md.Layout;

            if (string.IsNullOrEmpty(layoutName))
            {
                return(string.Empty);    //Use the default basic HTML5 template
            }
            //If both the template folder and the layout are established, then get the base folder for the templates
            //This base path for the templates parameter is only available through Web.config. NOT in the file Front Matter (we're skipping the file in the following call)
            string basePath = FieldValuesHelper.GetFieldValue("TemplatesBasePath", null, "~/Templates/");

            return(VirtualPathUtility.AppendTrailingSlash(basePath) + VirtualPathUtility.AppendTrailingSlash(templateName) + layoutName);
        }
예제 #2
0
        /// <summary>
        /// Gets any property for the file, specificaly defined (such as Title, Tags Categories...) or from the Front-Matter
        /// It won't process any special FM value such as File Processing Fields or Custom Front Matter Values
        /// </summary>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public override object this[object fieldName]
        {
            get
            {
                object res = base[fieldName];
                if (res == null)
                {
                    res = FieldValuesHelper.GetFieldObject(fieldName.ToString(), _md, null);
                }

                return(res);
            }
        }
예제 #3
0
        /// <summary>
        /// Gets if a specified property for the file exists or not, specificaly defined (such as Title, Tags Categories...) or from the Front-Matter
        /// It won't process any special FM value such as File Processing Fields or Custom Front Matter Values
        /// </summary>
        /// <param name="fieldName"></param>
        /// <returns></returns>
        public override bool ContainsKey(object fieldName)
        {
            bool hasProp = base.ContainsKey(fieldName);

            if (!hasProp)
            {
                object propVal = FieldValuesHelper.GetFieldObject(fieldName.ToString(), _md, null);
                if (propVal != null)
                {
                    hasProp = true;
                }
            }
            return(hasProp);
        }
예제 #4
0
        //Registers all custom Front-Matter sources inside the assembly passed as a parameter
        private static void RegisterCustomFMSourcesInAssembly(Assembly assembly)
        {
            //Custom FM sources are obtained from classes in the FMSources namespace that implement the IFMSource interface
            var fmSources = from c in assembly.GetTypes()
                            where c.IsClass && c.Namespace == CUSTOM_FMSOURCES_NAMESPACE && typeof(IFMSource).IsAssignableFrom(c)
                            select c;

            //Register each FMSource globally using its factory method (GetFilterType)
            fmSources.ToList().ForEach(fmSourceClass =>
            {
                IFMSource fms = (IFMSource)Activator.CreateInstance(fmSourceClass);

                //Register possible fields that will define different caches for the file
                if (fms is IQueryStringDependent)
                {
                    MarkdownFile.AddCachingQueryStringFields(
                        (fms as IQueryStringDependent).GetCachingQueryStringFields()
                        );
                }

                FieldValuesHelper.AddFrontMatterSource(fms.SourceName, fms.GetType());
            });
        }
예제 #5
0
        private static readonly string FILE_FRAGMENT_PREFIX = "*";  //How to identify fragments placeholders in content files
        #endregion

        #region Constructor
        static Renderer()
        {
            //Dynamically setup and add to DotLiquid template processor all the new custom tags, filters and FM sources
            RegisterCustomExtensions();

            //Configure DotLiquid (once)

            //Check if the CSharp Naming convention is to be used (RubyNamingConvention by default)
            //naming: csharp in the root web.config
            //(this is only set once for the whole application because it's an static property
            //of the DotLiquid template rendering engine)
            if (FieldValuesHelper.GetFieldValue("naming", null, "ruby") == "csharp")
            {
                Template.NamingConvention = new DotLiquid.NamingConventions.CSharpNamingConvention();
            }
            else
            {
                Template.NamingConvention = new DotLiquid.NamingConventions.RubyNamingConvention();
            }

            //Check which date formatting to use (Ruby/strftime or C#, C# by default)
            //DateFormat parameter
            Liquid.UseRubyDateFormat = (FieldValuesHelper.GetFieldValue("dateformat", null, "csharp") == "ruby");
        }
예제 #6
0
        //Retrieves the value for the specified field or returns an empty string if it doesn't exist
        protected override object GetValue(string name)
        {
            object res = "";    //Default value (empty string)

            switch (name.ToLowerInvariant())
            {
            //Check well Known fields first
            case INTERNAL_REFERENCE_TO_CURRENT_FILE:
                //This is intended to be used internally only, from custom tags or front-matter custom sources
                res = _mdProxy;
                break;

            case "content":                     //The final HTML content, WITHOUT the template and WITH liquid tags processed
                res = _parentFile.RawFinalHtml; //This is needed to avoid that the Markdown conversion messes up with the liquid tags (loops, conditionals...)
                break;

            case "title":
                res = _parentFile.Title;
                break;

            case "excerpt":
            case "description":
            case "summary":
                res = _parentFile.Excerpt;
                break;

            case "filename":
                res = _parentFile.FileName;
                break;

            case "filenamenoext":
                res = _parentFile.FileNameNoExt;
                break;

            case "fileext":
                res = _parentFile.FileExt;
                break;

            case "dir":
                res = _mdProxy.Dir;
                break;

            case "date":
                res = _parentFile.Date;
                break;

            case "datecreated":
                res = _parentFile.DateCreated;
                break;

            case "datemodified":
                res = _parentFile.DateLastModified;
                break;

            case "isauthenticated":
                res = _ctx.User.Identity.IsAuthenticated;
                break;

            case "authtype":
                res = _ctx.User.Identity.AuthenticationType;
                break;

            case "username":
                res = _ctx.User.Identity.Name;
                break;

            case "domain":
                res = _ctx.Request.Url.Authority;
                break;

            case "baseurl":
                res = $"{_ctx.Request.Url.Scheme}{System.Uri.SchemeDelimiter}{_ctx.Request.Url.Authority}";
                break;

            case "clientip":
                res = WebHelper.GetIPAddress();
                break;

            case "now":
            case "today":
                res = DateTime.Now;
                break;

            case "time":
                res = DateTime.Now.ToString("hh:mm:ss tt");
                break;

            case "url":
                res = _ctx.Request.RawUrl;
                break;

            case "urlnoext":
                res = IOHelper.RemoveFileExtension(_ctx.Request.RawUrl);
                //Files processed by MIIS always have extension on disk
                //res = _ctx.Request.Path.Remove(_ctx.Request.Path.LastIndexOf("."));
                break;

            case "templatename":
                res = _parentFile.TemplateName;
                break;

            case "layout":
                res = _parentFile.Layout;
                break;

            //Custom fields
            default:
                Exception exToBeRaised = null;                                     //Possible exceptions raised by the next code
                //Check if the custom field has already been retrieved before
                bool isCached = InternalFileFieldCache.TryGetValue(name, out res); //If it's cached the value will be saved to res
                if (!isCached)                                                     //If it's not cached (has not been retrieved before) then retrieve it
                {
                    res = string.Empty;                                            //Default value

                    /*
                     * There are 4 types of custom fields:
                     * - Value fields: {{name}} -> Get a value from the Front-Matter or from web.config -> Simply replace them (default assumption)
                     * - File processing fields (FPF), {{name}} whose value ends in .md or .mdh. ej: myfile.md -> if available the file is read and it's contents transformed into HTML take the place of the placeholder
                     *   Useful for menus, and other independent parts in custom templates and parts of the same page.
                     * - Custom Dinamic Field Sources, {{name}} value that start with !! and use a custom class to populate the field with an object. Ej: !!customSource param1 param2
                     * - Querystring or Form fields, retrieved from the current request
                     */

                    //Try to get a typed value for the field
                    var typedValue = FieldValuesHelper.GetFieldObject(name, _parentFile, null);
                    //If we get a string already, then process it
                    if (typedValue is string)
                    {
                        //If it's a string, process it for special fields

                        //////////////////////////////
                        //Simple value fields (default value if present)
                        //////////////////////////////

                        string resAsString = typedValue.ToString();

                        //////////////////////////////
                        //First, Custom Dinamic Field Sources that provide values from external assemblies
                        //////////////////////////////
                        if (resAsString.StartsWith(FRONT_MATTER_SOURCES_PREFIX))
                        {
                            //Get the name of the source and it's params splitting the string (the first element would be the name of the source, and the rest, the parameters, if any
                            string[] srcelements = resAsString.Substring(FRONT_MATTER_SOURCES_PREFIX.Length).Trim().Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
                            if (srcelements.Length > 0)
                            {
                                try
                                {
                                    res = FieldValuesHelper.GetFieldValueFromFMSource(srcelements[0], _mdProxy, srcelements.Skip(1).ToArray());
                                }
                                catch (Exception ex)
                                {
                                    //Throw the exception to be reflected in the
                                    exToBeRaised = ex;
                                }
                            }
                        }
                        //////////////////////////////
                        //Second, File Processing Fields, that inject the content of .md or .mdh processing their inner fields in their own context
                        //This is for compatbility reasons with MIIS v1.x and 2.x
                        //////////////////////////////
                        else if (resAsString.ToLowerInvariant().EndsWith(MarkdownFile.MARKDOWN_DEF_EXT) || resAsString.ToLowerInvariant().EndsWith(MarkdownFile.HTML_EXT))
                        {
                            //This kind of fields can only be processed in the first level of liquid tags processing.
                            //MustProcessSubFiles is false in the second level
                            //If this is a second level (a FPF inside another FPF) will just return an empty string (won't be processed at all)
                            if (_parentFile.MustProcessSubFiles)
                            {
                                try
                                {
                                    string       fpfPath      = _ctx.Server.MapPath(resAsString); //The File-Processing Field path
                                    MarkdownFile insertedFile = new MarkdownFile(fpfPath, false); //No processing of FPF beyond this layer is allowed to prevent circular references

                                    //If the parent file is a Markdown file
                                    if (_parentFile.FileExt == MarkdownFile.MARKDOWN_DEF_EXT)
                                    {
                                        //HACK: Since {{field}} placeholders are processed BEFORE transforming into HTML
                                        //I need to wrap the injected HTML into a special tag to prevent further processing of the resulting HTML when converting
                                        //the main file contents to HTML. The reason is that mixed HTML is very tricky and can lead to unexpected results
                                        //This is a sloppy way to do it, but it's the only way to mark a section of markdown as "not processable"
                                        //See: https://github.com/lunet-io/markdig/blob/master/src/Markdig.Tests/Specs/CommonMark.md#html-blocks
                                        //Later we need to remove these delimiters in a sloppy way too :-(
                                        res = HTML_NO_PROCESSING_DELIMITER_BEGIN +
                                              insertedFile.ComponentHtml +
                                              HTML_NO_PROCESSING_DELIMITER_END;
                                    }
                                    else
                                    {
                                        //If the parent file is already an HTML file, there's no need for the previous hack
                                        res = insertedFile.ComponentHtml;     //Use the final HTML (WITHOUT the template (except in components) and WITH the liquid tags processed in its own context)
                                    }

                                    //Add the processed file to the dependencies of the currently processed content file,
                                    //so that the file is invalidated when the FPF changes (if caching is enabled)
                                    //The FPF is already cached too if caching is enabled
                                    _parentFile.AddFileDependency(fpfPath);
                                }
                                catch (System.Security.SecurityException)
                                {
                                    res = String.Format("Can't access file for {0}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFFIX);
                                }
                                catch (System.IO.FileNotFoundException)
                                {
                                    res = String.Format("File not found for {0}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFFIX);
                                }
                                catch (Exception ex)
                                {
                                    //This should only happen while testing, never in production, so I send the exception's message
                                    res = String.Format("Error loading {0}: {1}", TemplatingHelper.PLACEHOLDER_PREFIX + name + TemplatingHelper.PLACEHOLDER_SUFFIX, ex.Message);
                                }
                            }
                        }
                        //////////////////////////////
                        //Finally, if it's not a custom source, or a FPF, then is a normal raw string value
                        //////////////////////////////
                        else
                        {
                            res = resAsString;
                        }
                    }
                    else
                    {
                        //If we already got a typed value, then return it by default
                        res = typedValue;

                        //Check if it's null. If it is null, means it hasn't been found in he fields for the file or app,
                        //so maybe the last chance is that it's a request param
                        if (typedValue == null)
                        {
                            //////////////////////////////
                            //Try to determine the param value from the querystring or the form values
                            //////////////////////////////
                            if (!string.IsNullOrWhiteSpace(_ctx.Request.Params[name]))
                            {
                                var paramVal = _ctx.Request.Params[name];     //Result (checks qs, form data, cookies and sv, in that order)
                                if (!string.IsNullOrWhiteSpace(paramVal))
                                {
                                    res = paramVal;                     //A value has been found
                                    _parentFile.CachingEnabled = false; //Disable caching if a param is to be used
                                }
                            }
                        }
                    }

                    //Cache the retrieved value
                    InternalFileFieldCache[name] = res;
                    //If there's been an exception, raise it to inform the renderer about it
                    if (exToBeRaised != null)
                    {
                        throw exToBeRaised;
                    }
                }
                //Get out of the switch construction
                break;
            }
            //Return retrieved value
            return(res);
        }