예제 #1
0
        //Finds fragment placeholders in the template and insert their contents
        //Fragments are placeholders that start with an asterisk("*") to indicate that instead of finding the value we should
        //look for a file with the same name as the current one and with the indicated suffix in their name.
        //If two (.md and .mdh) files exist with that name, the one with the same extension as the current file gets precedence
        //They allow to insert "fragments" of the same resulting file in the template positions we want.
        //The placeholders that they contain will be later parsed and processed as normal fields in the file
        private static string InjectFragments(string layoutHtml, MarkdownFile md, HttpContext ctx)
        {
            string tempContent = layoutHtml;

            string[] fragments = TemplatingHelper.GetAllPlaceHolderNames(tempContent, phPrefix: FILE_FRAGMENT_PREFIX);
            foreach (string fragmentName in fragments)
            {
                string fragmentContent  = string.Empty;                                                                                                            //Default empty value
                string fragmentFileName = ctx.Server.MapPath(Path.GetFileNameWithoutExtension(md.FileName) + fragmentName.Substring(FILE_FRAGMENT_PREFIX.Length)); //Removing the "*" at the beginning

                //Test if a file the same file extension exists
                if (File.Exists(fragmentFileName + md.FileExt))
                {
                    fragmentFileName += md.FileExt;
                }
                else  //Try with the other file extension (.md or .mdh depending of the current file's extension)
                {
                    fragmentFileName += (md.FileExt == MarkdownFile.MARKDOWN_DEF_EXT) ? MarkdownFile.HTML_EXT : MarkdownFile.MARKDOWN_DEF_EXT;
                }

                //Try to read the file with fragment
                try
                {
                    md.AddFileDependency(fragmentFileName);

                    MarkdownFile mdFld = new MarkdownFile(fragmentFileName);
                    //Render the file in the context of the parent file, no its own
                    fragmentContent = RenderLiquidTags(mdFld.RawContent, md);   //Render tags in raw content (Markdown or HTML)
                    //If it's Markdown, convert to HTML before substitution
                    if (md.FileExt == MarkdownFile.MARKDOWN_DEF_EXT)
                    {
                        fragmentContent = ConvertMarkdown2Html(fragmentContent, md.UseEmoji, md.EnabledMDExtensions);
                    }
                }
                catch
                {
                    //If something is wrong (normally the file does not exist) simply return an empty string
                    //We don't want to force this kind of files to always exist
                    fragmentContent = string.Empty;
                }
                //Replace the placeholder with the value
                tempContent = TemplatingHelper.ReplacePlaceHolder(tempContent, fragmentName, fragmentContent);
            }

            return(tempContent);
        }
예제 #2
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);
        }
예제 #3
0
 /// <summary>
 /// Allows a custom tag or FM source to add a new file or folder dependency to the current MD or MDH file caching
 /// </summary>
 /// <param name="filePath">The full path to the file or folder that the current file depends on</param>
 public void AddFileDependency(string filePath)
 {
     _md.AddFileDependency(filePath);
 }