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; }
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) { }