這是我自己實現ParseControl的。它遠沒有完成,所以不要指望太多。
結果的期望:
非服務器控件將被放入「LiteralControl」 S。
服務器支持的控件(RUNAT = 「服務器」):在的 「web.config」 定義
- 「HTMLLoader對象」 S從 「System.Web程序」 組件
- 控制
- 定製控件
其他(如用戶控件)應該很容易添加(我現在沒有在我的項目中使用它們)。
結果(如果有效)將與「Page.ParseControl」所做的相似,但不是相同的。
性能: 我使用一些緩存來加快速度,但在Windows上比「Page.ParseControl」慢了將近50%。 對於單聲道(用3.12.1測試),它現在實際上可用。
Prerequesites:
包括 「HtmlAgilityPack」 從http://htmlagilitypack.codeplex.com
用法:
String html = "<div runat=\"server\" id=\"test123\"></div>";
Control c = null;
try {
// Performance tip: The "doc" object can be cached, if the same HTML needs to be parsed again
var doc = new HtmlDocument();
doc.LoadHtml(html);
c = OwnParseControlEngine.Parse(doc);
}
catch {
c = Page.ParseControl(html); // Note: Will crash with Mono 3.x
}
代碼:
/// <summary>
/// Own implementation of "ParseControl". Returns XHTML (default) or HTML.
/// Custom controls from "Web.config" are supported (TODO: User controls and imports on Page are NOT).
/// </summary>
private class OwnParseControlEngine {
public class ParseException : System.Exception {
public ParseException(HtmlNode e)
: base("Unknown ASP.NET server-tag \"" + e.OriginalName + "\".") {
}
}
private static readonly String _systemWebNamespace;
private static readonly String _systemWebAssembly;
private static readonly Dictionary<String, LinkedList<TagPrefixInfo>> _controlsTagPrefixInfos = new Dictionary<String, LinkedList<TagPrefixInfo>>(); // Key is tag-prefix in lowercase
private class Factory {
public delegate Control CreateDel(HtmlNode e);
private readonly CreateDel _del;
public Boolean DropWhiteSpaceLiterals { get; private set; }
public Factory(CreateDel del, Boolean dropWhiteSpaceLiterals = false) {
this._del = del;
this.DropWhiteSpaceLiterals = dropWhiteSpaceLiterals;
}
public Control Create(HtmlNode e) {
return this._del.Invoke(e);
}
}
private static readonly Dictionary<String, Factory> _factories = new Dictionary<String, Factory>(); // Must be locked. Key is tag-name in lowercase.
static OwnParseControlEngine() {
// We cache the results to speed things up. "Panel" is only used to get assembly info.
_systemWebNamespace = typeof(Panel).Namespace;
_systemWebAssembly = typeof(Panel).Assembly.FullName;
var section = (PagesSection)WebConfigurationManager.OpenWebConfiguration("/").GetSection("system.web/pages");
foreach (TagPrefixInfo info in section.Controls) {
LinkedList<TagPrefixInfo> l;
if (!_controlsTagPrefixInfos.TryGetValue(info.TagPrefix, out l)) {
l = new LinkedList<TagPrefixInfo>();
_controlsTagPrefixInfos.Add(info.TagPrefix.ToLower(), l);
}
l.AddLast(info);
}
// Add HTML control types
_factories.Add("span", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("div", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("body", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("font", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("a", new Factory((e) => { return new HtmlAnchor(); }));
_factories.Add("button", new Factory((e) => { return new HtmlButton(); }));
_factories.Add("form", new Factory((e) => { return new HtmlForm(); }));
_factories.Add("input", new Factory((e) => {
switch (e.Attributes["type"].Value) {
case "button": return new HtmlInputButton();
case "checkbox": return new HtmlInputCheckBox();
case "file": return new HtmlInputFile();
case "hidden": return new HtmlInputHidden();
case "image": return new HtmlInputImage();
case "radio": return new HtmlInputRadioButton();
case "text": return new HtmlInputText();
case "password": return new HtmlInputPassword();
case "reset": return new HtmlInputReset();
case "submit": return new HtmlInputSubmit();
}
throw new ParseException(e);
}));
_factories.Add("select", new Factory((e) => { return new HtmlSelect(); }));
_factories.Add("table", new Factory((e) => { return new HtmlTable(); }, true)); // Adding literals not allowed
_factories.Add("tr", new Factory((e) => { return new HtmlTableRow(); }, true)); // Adding literals not allowed
_factories.Add("td", new Factory((e) => { return new HtmlTableCell(); }));
_factories.Add("textarea", new Factory((e) => { return new HtmlTextArea(); }));
_factories.Add("link", new Factory((e) => { return new HtmlLink(); }));
_factories.Add("meta", new Factory((e) => { return new HtmlMeta(); }));
_factories.Add("title", new Factory((e) => { return new HtmlTitle(); }));
_factories.Add("img", new Factory((e) => { return new HtmlImage(); }));
}
private static void ApplyHtmlControlAttributes(HtmlControl c, HtmlNode e) {
foreach (HtmlAttribute a in e.Attributes) {
if (a.Name == "id")
c.ID = a.Value;
else if (a.Name != "runat")
c.Attributes[a.OriginalName] = HttpUtility.HtmlDecode(a.Value);
}
}
private static void ApplyControlAttributes(Control c, HtmlNode e) {
if (c is WebControl && e.Attributes["style"] != null) {
String style = HttpUtility.HtmlDecode(e.Attributes["style"].Value);
foreach (String s in style.Split(new Char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
((WebControl)c).Style[s.Substring(0, s.IndexOf(':'))] = s.Substring(s.IndexOf(':') + 1);
}
foreach (PropertyInfo p in c.GetType().GetProperties()) {
if (p.CanRead && p.CanWrite && e.Attributes[p.Name] != null) {
try {
Object v = null;
if (p.PropertyType.IsEnum)
v = Enum.Parse(p.PropertyType, e.Attributes[p.Name].Value);
else if (p.PropertyType == typeof(String))
v = e.Attributes[p.Name].Value;
else if (p.PropertyType == typeof(Boolean))
v = Boolean.Parse(e.Attributes[p.Name].Value);
else if (p.PropertyType == typeof(Int32))
v = Int32.Parse(e.Attributes[p.Name].Value);
else if (p.PropertyType == typeof(Unit))
v = Unit.Parse(e.Attributes[p.Name].Value);
// TODO: More types?
if (v != null)
p.SetValue(c, v, null);
}
catch {
}
}
}
}
private static Control CreateServerControl(HtmlNode e, out Boolean dropWhiteSpaceLiterals) {
Factory cf;
lock (_factories) {
_factories.TryGetValue(e.Name, out cf);
}
if (cf == null) {
Int32 pos = e.Name.IndexOf(':');
if (pos != -1) {
String tagPrefix = e.Name.Substring(0, pos).ToLower();
String name = e.Name.Substring(pos + 1);
Type t = null;
// Try "System.Web" (default assembly)
if (tagPrefix == "asp")
t = Type.GetType(String.Format("{0}.{1}, {2}", _systemWebNamespace, name, _systemWebAssembly), false, true); // "Namespace.ClassName, Assembly"
if (t == null) {
// Try controls specified in "web.config"
LinkedList<TagPrefixInfo> l;
if (_controlsTagPrefixInfos.TryGetValue(tagPrefix, out l)) {
foreach (var info in l) {
// Custom controls
t = Type.GetType(String.Format("{0}.{1}, {2}", info.Namespace, name, info.Assembly), false, true); // "Namespace.ClassName, Assembly"
if (t != null)
break;
// TODO: User controls with tag.TagName, tag.Source
}
}
}
if (t != null) {
cf = new Factory((e2) => { return (Control)Activator.CreateInstance(t); });
lock (_factories) {
_factories[e.Name] = cf; // "Replace" instead of "Add", because another thread might have already added it since the lock above
}
}
}
}
if (cf == null)
throw new ParseException(e);
var c = cf.Create(e);
if (c is HtmlControl)
ApplyHtmlControlAttributes((HtmlControl)c, e);
else
ApplyControlAttributes(c, e);
dropWhiteSpaceLiterals = cf.DropWhiteSpaceLiterals;
return c;
}
private static void ParseChildren(Control parentC, HtmlNode currE, Boolean xhtml = true, Boolean dropWhiteSpaceLiterals = false) {
foreach (HtmlNode childE in currE.ChildNodes) {
Control newC = null, closeTag = null;
Boolean newDropWhiteSpaceLiterals = false;
if (childE.Attributes["runat"] != null && childE.Attributes["runat"].Value.ToLower() == "server") // Server control
newC = CreateServerControl(childE, out newDropWhiteSpaceLiterals);
else { // Literal control
switch (childE.Name) {
case "#text":
if (!dropWhiteSpaceLiterals || childE.InnerText.Trim().Length != 0)
newC = new LiteralControl(childE.InnerText);
break;
default:
String s = String.Format("<{0}", childE.OriginalName);
foreach (HtmlAttribute a in childE.Attributes)
s += String.Format(" {0}=\"{1}\"", a.OriginalName, a.Value);
s += ">";
switch (childE.Name) {
// List of void elements taken from http://www.programmerinterview.com/index.php/html5/void-elements-html5/
case "area": case "base": case "br": case "col": case "command": case "embed": case "hr": case "img": case "input":
case "keygen": case "link": case "meta": case "param": case "source": case "track": case "wbr":
if (xhtml)
s = s.Substring(0, s.Length - 1) + "/>";
newC = new LiteralControl(s);
break;
default:
newC = new PlaceHolder(); // Used as a wrapper to allow child-controls
newC.Controls.Add(new LiteralControl(s));
closeTag = new LiteralControl(String.Format("</{0}>", childE.OriginalName));
break;
}
break;
}
}
if (newC != null) {
parentC.Controls.Add(newC);
ParseChildren(newC, childE, xhtml, newDropWhiteSpaceLiterals);
if (closeTag != null)
newC.Controls.Add(closeTag);
}
}
}
private OwnParseControlEngine() {
}
/// <summary>
/// Parses the given HTML document and returns a Control.
/// Throws "ParseException" on error (TODO: Maybe others too).
/// </summary>
public static Control Parse(HtmlDocument doc) {
var c = new Control();
ParseChildren(c, doc.DocumentNode, false);
return c;
}
}
*徹底打破*是不足以形容的東西,請告訴我們得到的錯誤 – knocte 2014-11-03 21:43:55
現在我想出了幾個解決方法:要修復Mono 3.x(Ubuntu 14.04)上的「ParseControl」,我現在使用帶有臨時「ascx」文件的「LoadControl」(注意:仍然很慢)。爲了加快速度,我現在使用基於http://htmlagilitypack.codeplex.com/的自行編寫的「ParseControl」-Engine,它實際上比預期的更好,所以我可能會在這裏發佈它,一旦我認爲它「穩定」。 – Tobias81 2014-11-18 14:35:59