|
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
namespace System.ServiceModel.Dispatcher
{
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.ServiceModel.Web.Configuration;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Globalization;
using System.Web;
using System.Xml.Schema;
class HelpHtmlBuilder
{
const string HelpOperationPageUrl = "help/operations/{0}";
const string HtmlHtmlElementName = "{http://www.w3.org/1999/xhtml}html";
const string HtmlHeadElementName = "{http://www.w3.org/1999/xhtml}head";
const string HtmlTitleElementName = "{http://www.w3.org/1999/xhtml}title";
const string HtmlBodyElementName = "{http://www.w3.org/1999/xhtml}body";
const string HtmlBrElementName = "{http://www.w3.org/1999/xhtml}br";
const string HtmlPElementName = "{http://www.w3.org/1999/xhtml}p";
const string HtmlTableElementName = "{http://www.w3.org/1999/xhtml}table";
const string HtmlTrElementName = "{http://www.w3.org/1999/xhtml}tr";
const string HtmlThElementName = "{http://www.w3.org/1999/xhtml}th";
const string HtmlTdElementName = "{http://www.w3.org/1999/xhtml}td";
const string HtmlDivElementName = "{http://www.w3.org/1999/xhtml}div";
const string HtmlAElementName = "{http://www.w3.org/1999/xhtml}a";
const string HtmlPreElementName = "{http://www.w3.org/1999/xhtml}pre";
const string HtmlClassAttributeName = "class";
const string HtmlTitleAttributeName = "title";
const string HtmlHrefAttributeName = "href";
const string HtmlRelAttributeName = "rel";
const string HtmlIdAttributeName = "id";
const string HtmlNameAttributeName = "name";
const string HtmlRowspanAttributeName = "rowspan";
const string HtmlHeading1Class = "heading1";
const string HtmlContentClass = "content";
const string HtmlRequestXmlId = "request-xml";
const string HtmlRequestJsonId = "request-json";
const string HtmlRequestSchemaId = "request-schema";
const string HtmlResponseXmlId = "response-xml";
const string HtmlResponseJsonId = "response-json";
const string HtmlResponseSchemaId = "response-schema";
const string HtmlOperationClass = "operation";
public static XDocument CreateHelpPage(Uri baseUri, IEnumerable<OperationHelpInformation> operations)
{
XDocument document = CreateBaseDocument(SR2.GetString(SR2.HelpPageOperationsAt, baseUri));
XElement table = new XElement(HtmlTableElementName,
new XElement(HtmlTrElementName,
new XElement(HtmlThElementName, SR2.GetString(SR2.HelpPageUri)),
new XElement(HtmlThElementName, SR2.GetString(SR2.HelpPageMethod)),
new XElement(HtmlThElementName, SR2.GetString(SR2.HelpPageDescription))));
string lastOperation = null;
XElement firstTr = null;
int rowspan = 0;
foreach (OperationHelpInformation operationHelpInfo in operations.OrderBy(o => FilterQueryVariables(o.UriTemplate)))
{
string operationUri = FilterQueryVariables(operationHelpInfo.UriTemplate);
string description = operationHelpInfo.Description;
if (String.IsNullOrEmpty(description))
{
description = SR2.GetString(SR2.HelpPageDefaultDescription, BuildFullUriTemplate(baseUri, operationHelpInfo.UriTemplate));
}
XElement tr = new XElement(HtmlTrElementName,
new XElement(HtmlTdElementName, new XAttribute(HtmlTitleAttributeName, BuildFullUriTemplate(baseUri, operationHelpInfo.UriTemplate)),
new XElement(HtmlAElementName,
new XAttribute(HtmlRelAttributeName, HtmlOperationClass),
new XAttribute(HtmlHrefAttributeName, String.Format(CultureInfo.InvariantCulture, HelpOperationPageUrl, operationHelpInfo.Name)), operationHelpInfo.Method)),
new XElement(HtmlTdElementName, description));
table.Add(tr);
if (operationUri != lastOperation)
{
XElement td = new XElement(HtmlTdElementName, operationUri == lastOperation ? String.Empty : operationUri);
tr.AddFirst(td);
if (firstTr != null && rowspan > 1)
{
firstTr.Descendants(HtmlTdElementName).First().Add(new XAttribute(HtmlRowspanAttributeName, rowspan));
}
firstTr = tr;
rowspan = 0;
lastOperation = operationUri;
}
++rowspan;
}
if (firstTr != null && rowspan > 1)
{
firstTr.Descendants(HtmlTdElementName).First().Add(new XAttribute(HtmlRowspanAttributeName, rowspan));
}
document.Descendants(HtmlBodyElementName).First().Add(new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), SR2.GetString(SR2.HelpPageOperationsAt, baseUri)),
new XElement(HtmlPElementName, SR2.GetString(SR2.HelpPageStaticText)),
table));
return document;
}
public static XDocument CreateOperationHelpPage(Uri baseUri, OperationHelpInformation operationInfo)
{
XDocument document = CreateBaseDocument(SR2.GetString(SR2.HelpPageReferenceFor, BuildFullUriTemplate(baseUri, operationInfo.UriTemplate)));
XElement table = new XElement(HtmlTableElementName,
new XElement(HtmlTrElementName,
new XElement(HtmlThElementName, SR2.GetString(SR2.HelpPageMessageDirection)),
new XElement(HtmlThElementName, SR2.GetString(SR2.HelpPageFormat)),
new XElement(HtmlThElementName, SR2.GetString(SR2.HelpPageBody))));
RenderMessageInformation(table, operationInfo, true);
RenderMessageInformation(table, operationInfo, false);
XElement div = new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), SR2.GetString(SR2.HelpPageReferenceFor, BuildFullUriTemplate(baseUri, operationInfo.UriTemplate))),
new XElement(HtmlPElementName, operationInfo.Description),
XElement.Parse(SR2.GetString(SR2.HelpPageOperationUri, HttpUtility.HtmlEncode(BuildFullUriTemplate(baseUri, operationInfo.UriTemplate)))),
XElement.Parse(SR2.GetString(SR2.HelpPageOperationMethod, HttpUtility.HtmlEncode(operationInfo.Method))));
if (!String.IsNullOrEmpty(operationInfo.JavascriptCallbackParameterName))
{
div.Add(XElement.Parse(SR2.GetString(SR2.HelpPageCallbackText, HttpUtility.HtmlEncode(operationInfo.JavascriptCallbackParameterName))), table);
}
else
{
div.Add(table);
}
document.Descendants(HtmlBodyElementName).First().Add(div);
CreateOperationSamples(document.Descendants(HtmlDivElementName).First(), operationInfo);
return document;
}
public static XDocument CreateMethodNotAllowedPage(Uri helpUri)
{
XDocument document = CreateBaseDocument(SR2.GetString(SR2.HelpPageTitleText));
XElement div = new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), SR2.GetString(SR2.HelpPageTitleText)));
if (helpUri == null)
{
div.Add(new XElement(HtmlPElementName, SR2.GetString(SR2.HelpPageMethodNotAllowed)));
}
else
{
div.Add(XElement.Parse(SR2.GetString(SR2.HelpPageMethodNotAllowedWithLink, HttpUtility.HtmlEncode(helpUri.AbsoluteUri))));
}
document.Descendants(HtmlBodyElementName).First().Add(div);
return document;
}
public static XDocument CreateServerErrorPage(Uri helpUri, Exception error)
{
XDocument document = CreateBaseDocument(SR2.GetString(SR2.HelpPageRequestErrorTitle));
XElement div = new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), SR2.GetString(SR2.HelpPageRequestErrorTitle)));
if (helpUri == null)
{
if (error != null)
{
//TFS Bug 500275: it is not necessary to HtmlEncode the error.Message string here because XElement ctor will encode it.
div.Add(new XElement(HtmlPElementName, SR2.GetString(SR2.HelpServerErrorProcessingRequestWithDetails, error.Message)));
div.Add(new XElement(HtmlPElementName, error.StackTrace ?? String.Empty));
}
else
{
div.Add(new XElement(HtmlPElementName, SR2.GetString(SR2.HelpServerErrorProcessingRequest)));
}
}
else
{
string encodedHelpLink = HttpUtility.HtmlEncode(helpUri.AbsoluteUri);
if (error != null)
{
//TFS Bug 500275: XElement.Parse does not HtmlEncode the string passed to it, so we need to encode it before calling Parse.
string errorMessage = AppSettings.DisableHtmlErrorPageExceptionHtmlEncoding ? error.Message : HttpUtility.HtmlEncode(error.Message);
div.Add(XElement.Parse(SR2.GetString(SR2.HelpServerErrorProcessingRequestWithDetailsAndLink, encodedHelpLink, errorMessage)));
div.Add(new XElement(HtmlPElementName, error.StackTrace ?? String.Empty));
}
else
{
div.Add(XElement.Parse(SR2.GetString(SR2.HelpServerErrorProcessingRequestWithLink, encodedHelpLink)));
}
}
document.Descendants(HtmlBodyElementName).First().Add(div);
return document;
}
public static XDocument CreateEndpointNotFound(Uri helpUri)
{
XDocument document = CreateBaseDocument(SR2.GetString(SR2.HelpPageTitleText));
XElement div = new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), SR2.GetString(SR2.HelpPageTitleText)));
if (helpUri == null)
{
div.Add(new XElement(HtmlPElementName, SR2.GetString(SR2.HelpPageEndpointNotFound)));
}
else
{
div.Add(XElement.Parse(SR2.GetString(SR2.HelpPageEndpointNotFoundWithLink, HttpUtility.HtmlEncode(helpUri.AbsoluteUri))));
}
document.Descendants(HtmlBodyElementName).First().Add(div);
return document;
}
public static XDocument CreateTransferRedirectPage(string originalTo, string newLocation)
{
XDocument document = CreateBaseDocument(SR2.GetString(SR2.HelpPageTitleText));
XElement div = new XElement(HtmlDivElementName, new XAttribute(HtmlIdAttributeName, HtmlContentClass),
new XElement(HtmlPElementName, new XAttribute(HtmlClassAttributeName, HtmlHeading1Class), SR2.GetString(SR2.HelpPageTitleText)),
XElement.Parse(SR2.GetString(SR2.HelpPageRedirect, HttpUtility.HtmlEncode(originalTo), HttpUtility.HtmlEncode(newLocation))));
document.Descendants(HtmlBodyElementName).First().Add(div);
return document;
}
static XDocument CreateBaseDocument(string title)
{
return new XDocument(
new XDocumentType("html", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd", null),
new XElement(HtmlHtmlElementName,
new XElement(HtmlHeadElementName,
new XElement(HtmlTitleElementName, title),
new XElement("{http://www.w3.org/1999/xhtml}style", SR2.GetString(SR2.HelpPageHtml))),
new XElement(HtmlBodyElementName)));
}
static string FilterQueryVariables(string uriTemplate)
{
int variablesIndex = uriTemplate.IndexOf('?');
if (variablesIndex > 0)
{
return uriTemplate.Substring(0, variablesIndex);
}
return uriTemplate;
}
static void RenderMessageInformation(XElement table, OperationHelpInformation operationInfo, bool isRequest)
{
MessageHelpInformation info = isRequest ? operationInfo.Request : operationInfo.Response;
string direction = isRequest ? SR2.GetString(SR2.HelpPageRequest) : SR2.GetString(SR2.HelpPageResponse);
string nonLocalizedDirection = isRequest ? HtmlRequestXmlId : HtmlResponseXmlId;
if (info.BodyDescription != null)
{
table.Add(new XElement(HtmlTrElementName,
new XElement(HtmlTdElementName, direction),
new XElement(HtmlTdElementName, info.FormatString),
new XElement(HtmlTdElementName, info.BodyDescription)));
}
else
{
if (info.XmlExample != null || info.Schema != null)
{
XElement contentTd;
table.Add(new XElement(HtmlTrElementName,
new XElement(HtmlTdElementName, direction),
new XElement(HtmlTdElementName, "Xml"),
contentTd = new XElement(HtmlTdElementName)));
if (info.XmlExample != null)
{
contentTd.Add(new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestXmlId : HtmlResponseXmlId)), SR2.GetString(SR2.HelpPageExample)));
if (info.Schema != null)
{
contentTd.Add(",");
}
}
if (info.Schema != null)
{
contentTd.Add(new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestSchemaId : HtmlResponseSchemaId)), SR2.GetString(SR2.HelpPageSchema)));
}
}
if (info.JsonExample != null)
{
table.Add(new XElement(HtmlTrElementName,
new XElement(HtmlTdElementName, direction),
new XElement(HtmlTdElementName, "Json"),
new XElement(HtmlTdElementName,
new XElement(HtmlAElementName, new XAttribute(HtmlHrefAttributeName, "#" + (isRequest ? HtmlRequestJsonId : HtmlResponseJsonId)), SR2.GetString(SR2.HelpPageExample)))));
}
}
}
static void CreateOperationSamples(XElement element, OperationHelpInformation operationInfo)
{
if (operationInfo.Request.XmlExample != null)
{
element.Add(GenerateSampleXml(operationInfo.Request.XmlExample, SR2.GetString(SR2.HelpPageXmlRequest), HtmlRequestXmlId));
}
if (operationInfo.Request.JsonExample != null)
{
element.Add(AddSampleJson(operationInfo.Request.JsonExample, SR2.GetString(SR2.HelpPageJsonRequest), HtmlRequestJsonId));
}
if (operationInfo.Response.XmlExample != null)
{
element.Add(GenerateSampleXml(operationInfo.Response.XmlExample, SR2.GetString(SR2.HelpPageXmlResponse), HtmlResponseXmlId));
}
if (operationInfo.Response.JsonExample != null)
{
element.Add(AddSampleJson(operationInfo.Response.JsonExample, SR2.GetString(SR2.HelpPageJsonResponse), HtmlResponseJsonId));
}
if (operationInfo.Request.Schema != null)
{
element.Add(GenerateSampleXml(XmlSchemaToXElement(operationInfo.Request.Schema), SR2.GetString(SR2.HelpPageRequestSchema), HtmlRequestSchemaId));
int count = 0;
foreach (XmlSchema schema in operationInfo.Request.SchemaSet.Schemas())
{
if (schema.TargetNamespace != operationInfo.Request.Schema.TargetNamespace)
{
element.Add(GenerateSampleXml(XmlSchemaToXElement(schema), ++count == 1 ? SR2.GetString(SR2.HelpPageAdditionalRequestSchema) : null, HtmlRequestSchemaId));
}
}
}
if (operationInfo.Response.Schema != null)
{
element.Add(GenerateSampleXml(XmlSchemaToXElement(operationInfo.Response.Schema), SR2.GetString(SR2.HelpPageResponseSchema), HtmlResponseSchemaId));
int count = 0;
foreach (XmlSchema schema in operationInfo.Response.SchemaSet.Schemas())
{
if (schema.TargetNamespace != operationInfo.Response.Schema.TargetNamespace)
{
element.Add(GenerateSampleXml(XmlSchemaToXElement(schema), ++count == 1 ? SR2.GetString(SR2.HelpPageAdditionalResponseSchema) : null, HtmlResponseSchemaId));
}
}
}
}
private static XElement XmlSchemaToXElement(XmlSchema schema)
{
XmlWriterSettings settings = new XmlWriterSettings
{
CloseOutput = false,
Indent = true,
};
XDocument schemaDocument = new XDocument();
using (XmlWriter writer = XmlWriter.Create(schemaDocument.CreateWriter(), settings))
{
schema.Write(writer);
}
return schemaDocument.Root;
}
static XElement AddSample(object content, string title, string label)
{
if (String.IsNullOrEmpty(title))
{
return new XElement(HtmlPElementName,
new XElement(HtmlPreElementName, new XAttribute(HtmlClassAttributeName, label), content));
}
else
{
return new XElement(HtmlPElementName,
new XElement(HtmlAElementName, new XAttribute(HtmlNameAttributeName, label), title),
new XElement(HtmlPreElementName, new XAttribute(HtmlClassAttributeName, label), content));
}
}
static XElement GenerateSampleXml(XElement content, string title, string label)
{
StringBuilder sample = new StringBuilder();
using (XmlWriter writer = XmlTextWriter.Create(sample, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }))
{
content.WriteTo(writer);
}
return AddSample(sample.ToString(), title, label);
}
static XElement AddSampleJson(XElement content, string title, string label)
{
StringBuilder sample = new StringBuilder();
using (MemoryStream stream = new MemoryStream())
{
using (XmlJsonWriter writer = new XmlJsonWriter())
{
writer.SetOutput(stream, Encoding.Unicode, false);
content.WriteTo(writer);
}
stream.Position = 0;
sample.Append(new StreamReader(stream, Encoding.Unicode).ReadToEnd());
}
int depth = 0;
bool inString = false;
for (int i = 0; i < sample.Length; ++i)
{
if (sample[i] == '"')
{
inString = !inString;
}
else if (sample[i] == '{')
{
sample.Insert(i + 1, "\n" + new String('\t', ++depth));
i += depth + 1;
}
else if (sample[i] == ',' && !inString)
{
sample.Insert(i + 1, "\n" + new String('\t', depth));
}
else if (sample[i] == '}' && depth > 0)
{
sample.Insert(i, "\n" + new String('\t', --depth));
i += depth + 1;
}
}
return AddSample(sample.ToString(), title, label);
}
static string BuildFullUriTemplate(Uri baseUri, string uriTemplate)
{
UriTemplate template = new UriTemplate(uriTemplate);
Uri result = template.BindByPosition(baseUri, template.PathSegmentVariableNames.Concat(template.QueryValueVariableNames).Select(name => "{" + name + "}").ToArray());
return result.ToString();
}
}
}
|