예제 #1
0
        public async Task<Errorable<HtmlFragment>> RenderBlobAsync(CanonicalBlobPath path)
        {
            var mroot = await GetRoot();
            // The most popular error message will be...
            if (!mroot.HasValue) return new ConsistencyError("Either the site is not published or we have no frame of reference on which ");

            // Create a TreeBlobPath to root the blob path in the current root TreeID:
            var tbpath = new TreeBlobPath(mroot.Value, path);

            // Try getting the blob by its path:
            var eblob = await SystemContext.tpsbrepo.GetBlobByTreePath(tbpath);
            if (eblob.HasErrors) return eblob.Errors;
            var blob = eblob.Value.StreamedBlob;

            // Render the blob:
            var efragment = await Engine.RenderBlob(new TreePathStreamedBlob(tbpath, blob));
            if (efragment.HasErrors) return efragment.Errors;

            // Return its output:
            return efragment.Value;
        }
        public async Task<Errorable<TreePathStreamedBlob>> GetBlobByTreePath(TreeBlobPath treePath)
        {
            var etrm = await trrepo.GetTreeIDByPath(new TreeTreePath(treePath.RootTreeID, treePath.Path.Tree)).ConfigureAwait(continueOnCapturedContext: false);
            if (etrm.HasErrors) return etrm.Errors;
            
            TreeIDPathMapping trm = etrm.Value;
            if (!trm.TreeID.HasValue) return new BlobNotFoundByPathError(treePath);

            // Get the tree:
            var etr = await trrepo.GetTree(trm.TreeID.Value).ConfigureAwait(continueOnCapturedContext: false);
            if (etr.HasErrors) return etr.Errors;

            TreeNode tr = etr.Value;

            // Get the blob out of this tree:
            // TODO: standardize name comparison semantics:
            var trbl = tr.Blobs.SingleOrDefault(x => x.Name == treePath.Path.Name);
            if (trbl == null) return new BlobNotFoundByPathError(treePath);

            // Check for system inconsistency:
            if (!system.getPathByID(trbl.BlobID).Exists) return new BlobNotFoundByPathError(treePath);

            return new TreePathStreamedBlob(treePath, new StreamedBlob(blrepo, trbl.BlobID));
        }
 public TreePathStreamedBlob(TreeBlobPath treePath, IStreamedBlob blob)
 {
     this.TreeBlobPath = treePath;
     this.StreamedBlob = blob;
 }
        private async Task<Errorable> processImportTemplateElement(RenderState st)
        {
            // Imports content directly from another blob, addressable by a relative path or an absolute path.
            // Relative path is always relative to the current blob's absolute path.
            // In the case of nested imports, relative paths are relative to the absolute path of the importee's parent blob.

            // <cms-import-template path="/template/main">
            //   <area id="head">
            //     <link rel="" />
            //   </area>
            //   <area id="body">
            //     <div>
            //       ...
            //     </div>
            //   </area>
            // </cms-import-template>

            // Absolute paths are canonicalized. An exception will be thrown if the path contains too many '..' references that
            // bring the canonicalized path above the root of the tree (which is impossible).

            // Recursively call RenderBlob on the imported blob and include the rendered HTMLFragment into this rendering.

            var xr = st.Reader;

            // st.Reader is pointing to "cms-import-template" Element.

            if (!xr.HasAttributes || !xr.MoveToAttribute("path"))
            {
                st.Error("cms-import-template requires a 'path' attribute");
                st.SkipElementAndChildren("cms-import-template");
                return Errorable.NoErrors;
            }

            string ncpath = xr.Value;
            st.Reader.MoveToElement();

            TreePathStreamedBlob tmplBlob;

            // Canonicalize the absolute or relative path relative to the current item's path:
            var abspath = PathObjectModel.ParseBlobPath(ncpath);
            CanonicalBlobPath path = abspath.Collapse(abs => abs, rel => (st.Item.TreeBlobPath.Path.Tree + rel)).Canonicalize();

            // Fetch the Blob given the absolute path constructed:
            TreeBlobPath tbp = new TreeBlobPath(st.Item.TreeBlobPath.RootTreeID, path);
            var etmplBlob = await st.Engine.TreePathStreamedBlobs.GetBlobByTreePath(tbp).ConfigureAwait(continueOnCapturedContext: false);
            if (etmplBlob.HasErrors)
            {
                st.SkipElementAndChildren("cms-import-template");

                // Check if the error is a simple blob not found error:
                bool notFound = etmplBlob.Errors.Any(er => er is BlobNotFoundByPathError);
                if (notFound)
                {
                    st.Error("cms-import-template could not find blob by path '{0}' off tree '{1}'", tbp.Path.ToString(), tbp.RootTreeID.ToString());
                    return Errorable.NoErrors;
                }

                // Error was more serious:
                foreach (var err in etmplBlob.Errors.Errors)
                {
                    st.Error(err.Message);
                }
                return etmplBlob.Errors;
            }
            else tmplBlob = etmplBlob.Value;

            Debug.Assert(tmplBlob != null);

            // This lambda processes the entire imported template:
            Func<RenderState, Task<Errorable<bool>>> processElements = (Func<RenderState, Task<Errorable<bool>>>)(async sst =>
            {
                // Make sure cms-template is the first element from the imported template blob:
                if (sst.Reader.LocalName != "cms-template")
                {
                    sst.Error("cms-import-template expected cms-template as first element of imported template");
                    sst.SkipElementAndChildren(sst.Reader.LocalName);
                    st.SkipElementAndChildren("cms-import-template");
                    return false;
                }

                // Don't move the st.Reader yet until we know the cms-import-template has a cms-template-area in it:
                string fillerAreaId = null;
                bool isFirstArea = !st.Reader.IsEmptyElement;

                // Create a new RenderState that reads from the parent blob and writes to the template's renderer:
                var stWriter = new RenderState(st.Engine, st.Item, st.Reader, sst.Writer);

                // This lambda is called recursively to handle cms-template-area elements found within parent cms-template-area elements in the template:
                Func<RenderState, Task<Errorable<bool>>> processTemplateAreaElements = null;
                processTemplateAreaElements = (Func<RenderState, Task<Errorable<bool>>>)(async tst =>
                {
                    // Only process cms-template-area elements:
                    if (tst.Reader.LocalName != "cms-template-area")
                    {
                        // Use DefaultProcessElements to handle processing other cms- custom elements from the template:
                        return await RenderState.DefaultProcessElements(tst);
                    }

                    // Read the cms-template-area's id attribute:
                    if (!tst.Reader.MoveToAttribute("id"))
                    {
                        tst.Error("cms-template-area needs an 'id' attribute");
                        tst.Reader.MoveToElement();
                        tst.SkipElementAndChildren("cms-template-area");
                        return false;
                    }

                    // Assign the template's area id:
                    string tmplAreaId = tst.Reader.Value;

                    // Move to the first area if we have to:
                    if (isFirstArea)
                    {
                        if (!st.Reader.IsEmptyElement)
                        {
                            fillerAreaId = moveToNextAreaElement(st);
                        }
                        isFirstArea = false;
                    }

                    // Do the ids match?
                    if ((fillerAreaId != null) && (tmplAreaId == fillerAreaId))
                    {
                        // Skip the cms-template-area in the template:
                        tst.Reader.MoveToElement();
                        tst.SkipElementAndChildren("cms-template-area");
                        // Move the filler reader to the element node:
                        st.Reader.MoveToElement();
                        // Copy the elements:
                        await stWriter.CopyElementChildren("area");

                        // Move to the next area element, if available:
                        fillerAreaId = moveToNextAreaElement(st);
                    }
                    else
                    {
                        // Insert the default content from the template:
                        tst.Reader.MoveToElement();
                        // Recurse into children, allowing processing of embedded cms-template-areas:
                        await tst.CopyElementChildren("cms-template-area", null, processTemplateAreaElements);
                    }

                    // We handled this:
                    return false;
                });

                // Now continue on stream-copying child elements until we find a cms-template-area:
                var err = await sst.CopyElementChildren("cms-template", null, processTemplateAreaElements)
                    .ConfigureAwait(continueOnCapturedContext: false);
                if (err.HasErrors) return err.Errors;

                // We missed some <area />s in the cms-import-template:
                while (!((st.Reader.NodeType == XmlNodeType.EndElement) && (st.Reader.LocalName == "cms-import-template")) &&
                       !((st.Reader.NodeType == XmlNodeType.Element) && st.Reader.IsEmptyElement && (st.Reader.LocalName == "cms-import-template")))
                {
                    // Move to the next <area /> start element:
                    fillerAreaId = moveToNextAreaElement(st);
                    if (fillerAreaId != null)
                    {
                        st.Warning("area '{0}' unused by the template", fillerAreaId);
                        st.SkipElementAndChildren("area");
                    }
                }

                return false;
            });

            // Process the imported cms-template and inject the <area /> elements from the current <cms-import-template /> element:
            RenderState importedTemplate = new RenderState(
                st.Engine,
                tmplBlob,

                earlyExit: (Func<RenderState, bool>)(sst =>
                {
                    return false;
                }),

                processElements: processElements,
                previous: st
            );

            // Render the template:
            var esbResult = await importedTemplate.Render().ConfigureAwait(continueOnCapturedContext: false);
            if (esbResult.HasErrors)
            {
                foreach (var err in esbResult.Errors)
                    st.Error(err.Message);
                return esbResult.Errors;
            }

            StringBuilder sbResult = esbResult.Value;
            string blob = sbResult.ToString();

            // Write the template to our current writer:
            st.Writer.Append(blob);

            return Errorable.NoErrors;
        }
예제 #5
0
        private async Task<Errorable> processImportElement(RenderState st)
        {
            // Imports content directly from another blob, addressable by a relative path or an absolute path.
            // Relative path is always relative to the current blob's absolute path.
            // In the case of nested imports, relative paths are relative to the absolute path of the importee's parent blob.

            // <cms-import path="../templates/main" />
            // <cms-import path="/templates/main" />

            // Absolute paths are canonicalized. An exception will be thrown if the path contains too many '..' references that
            // bring the canonicalized path above the root of the tree (which is impossible).

            // Recursively call RenderBlob on the imported blob and include the rendered HTMLFragment into this rendering.

            // st.Reader is pointing to "cms-import" Element.
            if (!st.Reader.IsEmptyElement) st.Error("cms-import element must be empty");

            if (st.Reader.HasAttributes && st.Reader.MoveToFirstAttribute())
            {
                string ncpath = st.Reader.GetAttribute("path");
                string blob;
                TreePathStreamedBlob tpsBlob;

                // Fetch the TreePathStreamedBlob for the given path:
                // Canonicalize the absolute or relative path relative to the current item's path:
                var abspath = PathObjectModel.ParseBlobPath(ncpath);
                CanonicalBlobPath path = abspath.Collapse(abs => abs, rel => (st.Item.TreeBlobPath.Path.Tree + rel)).Canonicalize();

                // Fetch the Blob given the absolute path constructed:
                TreeBlobPath tbp = new TreeBlobPath(st.Item.TreeBlobPath.RootTreeID, path);
                var etpsBlob = await st.Engine.TreePathStreamedBlobs.GetBlobByTreePath(tbp).ConfigureAwait(continueOnCapturedContext: false);
                if (etpsBlob.HasErrors)
                {
                    st.SkipElementAndChildren("cms-import");

                    // Check if the error is a simple blob not found error:
                    bool notFound = etpsBlob.Errors.Any(er => er is BlobNotFoundByPathError);
                    if (notFound)
                    {
                        st.Error("cms-import could not find blob by path '{0]' off tree '{1}'", tbp.Path, tbp.RootTreeID);
                        return Errorable.NoErrors;
                    }

                    // Error was more serious:
                    foreach (var err in etpsBlob.Errors)
                        st.Error(err.Message);
                    return etpsBlob.Errors;
                }
                else tpsBlob = etpsBlob.Value;

                Debug.Assert(tpsBlob != null);

                // Fetch the contents for the given TreePathStreamedBlob:
                // TODO: we could probably asynchronously load blobs and render their contents
                // then at a final sync point go in and inject their contents into the proper
                // places in each imported blob's parent StringBuilder.

                // Render the blob inline:
                RenderState rsInner = new RenderState(st.Engine, tpsBlob);
                var einnerSb = await rsInner.Render().ConfigureAwait(continueOnCapturedContext: false);
                if (einnerSb.HasErrors)
                {
                    foreach (var err in einnerSb.Errors)
                        st.Error(err.Message);
                    return einnerSb.Errors;
                }

                blob = einnerSb.Value.ToString();

                st.Writer.Append(blob);

                // Move the reader back to the element node:
                st.Reader.MoveToElement();
            }

            return Errorable.NoErrors;
        }
 public BlobNotFoundByPathError(TreeBlobPath path) : base("A blob was not found given path '{0}'", path) { }
예제 #7
0
 public TreePathStreamedBlob(TreeBlobPath treePath, IStreamedBlob blob)
 {
     this.TreeBlobPath = treePath;
     this.StreamedBlob = blob;
 }