最早接触xml配置文件驱动事件是在实习的时候,那时候恶毒的项目经理让我研究一个工作流引擎,鼎鼎大名的OSWORKFLOW,那东西是JAVA写的,唉~~太强人所难了....最后当然是可耻的失败鸟。
最近在研究业务架构的时候,偶尔发现了一个XML引擎驱动事件的程序,fm2.0的,倒不是为了报仇,只是我一直没弄明白到底xml配置是怎么驱动事件的。就看了看,多少有点收获,跟大家分享一下
工欲善其事,必先利其器,所以首先,要有个好的XML读写方法集,我看了看好多大家可能都用得上,就放在这了, 当个参考资料


Instance Data members#region Instance Data members

/**//// <summary>
/// File name to read from
/// </summary>
string m_sFilename = "";


/**//// <summary>
/// Rules engine to populate
/// </summary>
RulesEngine m_oRulesEngine;


/**//// <summary>
/// Namespace manager to use in XPath
/// </summary>
XmlNamespaceManager m_oNSMgr;


/**//// <summary>
/// Xml DOM document to use in reading
/// </summary>
XmlDocument m_oXmlDoc;
#endregion


Ctors#region Ctors

/**//// <summary>
/// Default Constructor
/// </summary>
public JaxlabReader()

{
m_oRulesEngine = new RulesEngine();
}


/**//// <summary>
/// Constructor that also specifies the XML file name.
/// </summary>
/// <remarks>
/// This constructor calls the LoadEngineByFileName method.
/// </remarks>
/// <param name="sFilename">the XML file location (including UNC or HTTP)</param>
public JaxlabReader(string sFilename)

{
m_oRulesEngine = new RulesEngine();
LoadEngineByFileName(sFilename);
}
#endregion


/**//// <summary>
/// Loads the
/// </summary>
/// <param name="sFilename">the XML file location (including UNC or HTTP)</param>
public void LoadEngineByFileName(string sFilename)

{
try

{
m_sFilename = sFilename;
XmlDocument oXmlDoc = new XmlDocument();
oXmlDoc.Load(m_sFilename);
LoadEngineByXmlDoc(oXmlDoc);
}
catch(Exception err)

{
System.Console.WriteLine(err.Message + "\n" + err.StackTrace);
}
}


/**//// <summary>
/// Loads the Rules Engine by in-memory XmlDocument.
/// </summary>
/// <param name="oXmlDoc">the in-memory XmlDocument</param>
public void LoadEngineByXmlDoc(XmlDocument oXmlDoc)

{
try

{
m_oXmlDoc = oXmlDoc;
m_oNSMgr = new XmlNamespaceManager(m_oXmlDoc.NameTable);

LoadXmlVariables(m_oXmlDoc.DocumentElement);

XmlNodeList oRuleNodeList = m_oXmlDoc.SelectNodes(".//Rules/Rule", m_oNSMgr);
for(int i = 0; i < oRuleNodeList.Count; i++)

{
XmlNode oXmlRule = oRuleNodeList[i];
LoadXMLRule(null, oXmlRule);
}

XmlNode oTriggersNode = m_oXmlDoc.SelectSingleNode("//Triggers", m_oNSMgr);
if(oTriggersNode != null)

{
LoadTrigger(oTriggersNode);
}
}
catch(Exception err)

{
System.Console.WriteLine(err.Message + "\n" + err.StackTrace);
}
}


/**//// <summary>
/// Loads an XML Node into the Rules Engine.
/// </summary>
/// <param name="oXmlRule">XmlNode of a RulesObject</param>
public void LoadXmlRule(XmlNode oXmlRule)

{
LoadXMLRule(null, oXmlRule);
}

private void LoadXmlVariables(XmlNode oXmlRoot)

{
XmlNodeList oXmlVars = oXmlRoot.SelectNodes(".//Variable");
for(int i = 0; i < oXmlVars.Count; i++)

{
XmlNode oXmlVar = oXmlVars[i];
string sVarID = oXmlVar.Attributes.GetNamedItem("id").Value;
string sValue = oXmlVar.InnerText;

Variable oVar = new Variable(sVarID, sValue);
m_oRulesEngine.AddVariable(oVar);
}
}

private void LoadXMLRule(RuleObject oParentRule, XmlNode oXmlRule)

{
string sID = oXmlRule.Attributes.GetNamedItem("id").Value;
XmlNode xmlAttrUse = oXmlRule.Attributes.GetNamedItem("useInferencing");
XmlNode oExpr = oXmlRule.SelectSingleNode("Expression", m_oNSMgr);

if(sID != "" && oExpr != null)

{
RuleObject oRule = new RuleObject(sID, oExpr.InnerText);
if(xmlAttrUse != null)

{
string sUse = xmlAttrUse.Value;
string sUseAdj = sUse.Trim().ToLower();
if(sUseAdj.CompareTo("true") == 0)

{
oRule.UseInferencing = true;
}
else

{
oRule.UseInferencing = false;
}
}

XmlNode oXmlActions = oXmlRule.SelectSingleNode("Actions", m_oNSMgr);
XmlNodeList oXmlActionList = oXmlActions.SelectNodes("./Action", m_oNSMgr);
for(int a = 0; a < oXmlActionList.Count; a++)

{
XmlNode oXmlAction = oXmlActionList[a];
Action oAction = MakeAction(oXmlAction);

oRule.AddAction(oAction);
}


XmlNode oXmlSubRules = oXmlRule.SelectSingleNode("SubRules", m_oNSMgr);
XmlNodeList oXmlSubRuleList = oXmlSubRules.SelectNodes("./Rule", m_oNSMgr);
for(int s = 0; s < oXmlSubRuleList.Count; s++)

{
XmlNode oXmlSubRule = oXmlSubRuleList[s];
LoadXMLRule(oRule, oXmlSubRule);
}

if(oParentRule != null)

{
oParentRule.AddSubRule(oRule);
}
else

{
this.m_oRulesEngine.Add(oRule);
}
}
}

private Action MakeAction(XmlNode oXmlAction)

{
XmlNode oMatch = oXmlAction.SelectSingleNode("Match", m_oNSMgr);
XmlNode oAssign = oXmlAction.SelectSingleNode("Assign", m_oNSMgr);

string sMatch = "";
string sAssign = "";

if(oMatch != null)

{
sMatch = oMatch.InnerText;
}
string sAssignType = "none";
if(oAssign != null)

{
XmlNode oAssignType = oAssign.Attributes.GetNamedItem("type");
if(oAssignType != null)

{
sAssignType = oAssignType.Value;
}
sAssign = oAssign.InnerText;
}

string sActionID = oXmlAction.Attributes.GetNamedItem("actionId").InnerText;
string sType = oXmlAction.Attributes.GetNamedItem("type").InnerText;

Action.ActionType oType = ParseActionType(sType);

Action oAction = new Action(sAssignType, sMatch, sAssign, oType, sActionID);
if(sAssignType.CompareTo("object") == 0)

{
XmlNode oXmlNodeAssign = oAssign.FirstChild;

XMLNodeWrapper oWrapped = new XMLNodeWrapper(oXmlNodeAssign);
oAction = new Action(sAssignType, sMatch, oWrapped, oType, sActionID);
}

return oAction;
}

private Action.ActionType ParseActionType(string sType)

{
if(sType.ToLower().CompareTo("var") == 0)

{
return Action.ActionType.Var;
}
else if(sType.ToLower().CompareTo("makevar") == 0)

{
return Action.ActionType.MakeVar;
}
else if(sType.ToLower().CompareTo("run") == 0)

{
return Action.ActionType.Run;
}
else if(sType.ToLower().CompareTo("stop") == 0)

{
return Action.ActionType.Stop;
}
else

{
return Action.ActionType.None;
}

}


Trigger Loading#region Trigger Loading
private void LoadTrigger(XmlNode oXmlNode)

{
XmlNodeList oXTriggers = oXmlNode.ChildNodes;
for(int i = 0; i < oXTriggers.Count; i++)

{
XmlNode oXTrigger = oXTriggers[i];
XmlNode oIDAttr = oXTrigger.Attributes.GetNamedItem("id");
XmlNode oVarIDAttr = oXTrigger.Attributes.GetNamedItem("varID");
XmlNode oRuleIDAttr = oXTrigger.Attributes.GetNamedItem("ruleID");

string sID = GetAttrValue(oIDAttr);
string sVarID = GetAttrValue(oVarIDAttr);
string sRuleID = GetAttrValue(oRuleIDAttr);

Trigger oTrigger = new Trigger(sID, sVarID, sRuleID);

this.m_oRulesEngine.AddTrigger(oTrigger);
}
}
#endregion

private string GetAttrValue(XmlNode oAttrNode)

{
if(oAttrNode != null)

{
return oAttrNode.Value;
}
else

{
return "";
}
}


Properties#region Properties

/**//// <summary>
/// The RulesEngine object to use with your project.
/// </summary>
public RulesEngine NewRulesEngine

{
get

{
return this.m_oRulesEngine;
}
set

{
this.m_oRulesEngine = value;
}
}
#endregion
这是一段对应的XML配置文件片断
<Rule id="CalculateTotalPricing" useInferencing="False">
<Expression />
<Actions>
<Action actionId="StarTotalPrice" type="var">
<Match />
<Assign>(StarQty * StarPrice) - ((StarQty * StarPrice) * StarQtyDiscount)</Assign>
</Action>
由此可以看出,读写功能都是基于结构化维护XML配置文件的,根据配置文件的格式把定制文件内的信息读入到引擎中,与给真实引擎添加燃料不同,从程序上讲就是用适当的业务逻辑制定引擎的具体运转方式,以便在驱动事件运行时根据业务逻辑作出正确的判断,以及按照要求运算和处理数据。随之而来的就是对业务规则对象的定义
[Serializable]
public class RuleObject: IComparable

{

Instance data members#region Instance data members

/**//// <summary>
/// Id of this Rule
/// </summary>
string m_sID = "";


/**//// <summary>
/// Expression of this Rule
/// </summary>
string m_sExpression = "";


/**//// <summary>
/// Action collection
/// </summary>
ArrayList m_oActionList = new ArrayList();


/**//// <summary>
/// Sub-Rule collection
/// </summary>
SubRulesCollection m_oSubRules = new SubRulesCollection();


/**//// <summary>
/// Bool if this Rule is to be used in Inferencing
/// </summary>
bool m_bUseInferencing = false;


/**//// <summary>
/// Parent Rule of this Rule.
/// </summary>
RuleObject m_oParentRule = null;
#endregion


Properties#region Properties

/**//// <summary>
/// Property for the existence of an Expression in this RuleObject.
/// </summary>
public bool HasExpression

{
get

{
if(this.m_sExpression == "")

{
return false;
}
else

{
return true;
}
}
}


/**//// <summary>
/// Sets the bool for using inferencing.
/// </summary>
public bool UseInferencing

{
get

{
return m_bUseInferencing;
}
set

{
m_bUseInferencing = value;
foreach(RuleObject oRule in this.m_oSubRules)

{
oRule.UseInferencing = value;
}
}
}


/**//// <summary>
/// If any Rules have been added to this RuleObject then it is True, False otherwise
/// </summary>
public bool HasSubRules

{
get

{
return m_oSubRules.HasRules;
}
}


/**//// <summary>
/// The ID that this RuleObject is referenced by. Without an ID this RuleObject cannot be found.
/// </summary>
public string ID

{
get

{
return m_sID;
}
set

{
m_sID = value;
}
}


/**//// <summary>
/// The expression this RuleObject will use.
/// </summary>
public string Expression

{
get

{
return m_sExpression;
}
set

{
m_sExpression = value;
}
}


/**//// <summary>
/// Get the containing RuleObject of this RuleObject.
/// </summary>
public RuleObject ParentRuleObject

{
get

{
return m_oParentRule;
}
set

{
m_oParentRule = value;
}
}
#endregion


Ctors#region Ctors

/**//// <summary>
/// Default Constructor
/// </summary>
public RuleObject()

{
}


/**//// <summary>
/// Constructor with ID and Expression
/// </summary>
/// <param name="sID">The ID for this RuleObject</param>
/// <param name="sExpression">The expression for this RuleObject</param>
public RuleObject(string sID, string sExpression)

{
m_sID = sID;
m_sExpression = sExpression;
}
#endregion


Publics#region Publics

/**//// <summary>
/// Concatenates and returns the RuleID Path for this Rule
/// </summary>
/// <returns>string of concatenation of the Rule path of this rule (dot delmited)</returns>
public string GetPathID()

{
if(this.m_oParentRule != null)

{
StringBuilder oSb = new StringBuilder();
RuleObject oParent = this.m_oParentRule;
while(oParent != null)

{
oSb.Insert(0, oParent.ID + ".");
oParent = oParent.ParentRuleObject;
}
oSb.Append(m_sID);
return oSb.ToString();
}
else

{
return this.m_sID;
}
}
#endregion


Action Collection management#region Action Collection management

/**//// <summary>
/// Adds a Action object to this RuleObject
/// </summary>
/// <param name="oAction">The action object</param>
public void AddAction(Action oAction)

{
if(oAction.TypeAction == Action.ActionType.Var)

{
//Inference oInf = new Inference(
}
m_oActionList.Add(oAction);
}


/**//// <summary>
/// Get the number of Actions this RuleObject has
/// </summary>
/// <returns>The count of the Actions for this RuleObject</returns>
public int GetActionCount()

{
return m_oActionList.Count;
}
#endregion


Indexer#region Indexer

/**//// <summary>
/// Indexer for Actions in this RuleObject. (Index based)
/// </summary>
public Action this[int i]

{
get

{
return (Action) m_oActionList[i];
}
set

{
m_oActionList[i] = value;

}
}
#endregion


IComparable Members#region IComparable Members


/**//// <summary>
/// The CompareTo method is used to Sort the RuleObject Collection. The ID is the Compareto object.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)

{
if(obj is RuleObject)

{
RuleObject oRule = (RuleObject) obj;
return String.Compare(m_sID, oRule.ID, true);
}
else if(obj is string)

{
string sObj = obj.ToString();
return String.Compare(m_sID, sObj, true);
}
else

{
return -1;
}
}

#endregion


Sub-RuleObject Mangement#region Sub-RuleObject Mangement

/**//// <summary>
/// Adds a RuleObject to this RuleObject
/// </summary>
/// <param name="oRule"></param>
/// <returns></returns>
public void AddSubRule(RuleObject oRule)

{
oRule.ParentRuleObject = this;
oRule.UseInferencing = this.m_bUseInferencing;
m_oSubRules.Add(oRule);
}


/**//// <summary>
/// Remove a RuleObject from this RuleObject by Index.
/// </summary>
/// <param name="iIndex"></param>
public void RemoveRule(int iIndex)

{
try

{
m_oSubRules.RemoveAt(iIndex);
}
catch

{
}
}


/**//// <summary>
/// The number of SubRules this RuleObject has. 0 is there are none.
/// </summary>
/// <returns></returns>
public int GetSubRuleCount()

{
return m_oSubRules.Count;
}


/**//// <summary>
/// Gets the RuleObject from the SubRules collection that corresponds to the index.
/// </summary>
/// <param name="iIndex"></param>
/// <returns></returns>
public RuleObject GetSubRule(int iIndex)

{
RuleObject oRule = m_oSubRules[iIndex];
if(oRule != null)

{
return oRule;
}
else

{
throw new RuleNotFoundException();
}
}


/**//// <summary>
/// Gets Rule contained in the Sub-Rules of this Rule
/// </summary>
/// <param name="sRuleID">Id of Rule to find</param>
/// <returns>The found Rule</returns>
public RuleObject GetSubRule(string sRuleID)

{
RuleObject oRule = m_oSubRules[sRuleID];
if(oRule != null)

{
return oRule;
}
else

{
throw new RuleNotFoundException();
}
}


/**//// <summary>
/// Replace a SubRule with the supplied RuleObject
/// </summary>
/// <param name="iIndex"></param>
/// <param name="oRule"></param>
public void SetRule(int iIndex, RuleObject oRule)

{
RuleObject oFoundRule = m_oSubRules[iIndex];
if(oRule != null)

{
m_oSubRules[iIndex] = oFoundRule;
}
else

{
throw new RuleNotFoundException();
}
}


/**//// <summary>
/// Replaces the Rule identified by Id with another Rule
/// </summary>
/// <param name="sRuleID">Rule Id to replace</param>
/// <param name="oRule">new Rule for this ID</param>
public void SetRule(string sRuleID, RuleObject oRule)

{
RuleObject oFoundRule = m_oSubRules[sRuleID];
if(oRule != null)

{
m_oSubRules[sRuleID] = oFoundRule;
}
else

{
throw new RuleNotFoundException();
}
}
#endregion
}
同RULE同在的就是ACTION,不一定每个RULE都含有ACTION,但没有ACTION的RULE是模型而不是实体所以:
public class Action

{

Instance data members#region Instance data members

/**//// <summary>
/// Assigment expression string
/// </summary>
string m_sAssignStr = "";


/**//// <summary>
/// Target expression string
/// </summary>
string m_sTargetStr = "";


/**//// <summary>
/// Variable Target (uses operator overrides)
/// </summary>
Variable m_oTargetVar = null;


/**//// <summary>
/// Variable Assign (uses operator overrides)
/// </summary>
Variable m_oAssignVar = null;


/**//// <summary>
/// ID of this Action
/// </summary>
string m_sID = "";


/**//// <summary>
/// Public Enum of Action Types
/// </summary>
public enum ActionType

{

/**//// <summary>
/// Nothing occurs from this ActionType
/// </summary>
None,

/**//// <summary>
/// This ActionType is used to "Run" a RuleObject
/// </summary>
Run,

/**//// <summary>
/// This ActionType is used to re-assign a Variable
/// </summary>
Var,

/**//// <summary>
/// This ActionType is used to create a Variable
/// </summary>
MakeVar,

/**//// <summary>
/// This ActionType stops the processing of any more Actions
/// </summary>
Stop
}


/**//// <summary>
/// The ActionType of this Action
/// </summary>
public ActionType m_oType = ActionType.None;
#endregion


Properties#region Properties

/**//// <summary>
/// Property of the ActionType
/// </summary>
public ActionType TypeAction

{
get

{
return m_oType;
}
set

{
m_oType = value;
}
}

/**//// <summary>
/// The ID of this Action
/// </summary>
public string ID

{
get

{
return m_sID;
}
set

{
m_sID = value;
}
}

public string AssignString

{
get

{
return m_sAssignStr;
}
set

{
m_sAssignStr = value;
}
}

public string TargetString

{
get

{
return m_sTargetStr;
}
set

{
m_sTargetStr = value;
}
}


/**//// <summary>
/// The Target value as a Variable object
/// </summary>
public Variable TargetVariable

{
get

{
return m_oTargetVar;
}
set

{
m_oTargetVar = value;
}
}


/**//// <summary>
/// The Assign value as a Variable
/// </summary>
public Variable AssignVariable

{
get

{
return m_oAssignVar;
}
set

{
m_oAssignVar = value;
}
}


/**//// <summary>
/// The Target value as a string
/// </summary>
public object VariableValue

{
set

{
this.m_oTargetVar.StringValue = "" + value;
}
}
#endregion


Ctors#region Ctors

/**//// <summary>
/// Default Constructor
/// </summary>
public Action()

{
}


/**//// <summary>
/// Constructor with a know Target Value
/// </summary>
/// <param name="sTargetValue">Value to compare to the result of the RuleObject Expression</param>
public Action(string sTargetValue)

{
m_sTargetStr = sTargetValue;
m_oTargetVar = new Variable("var", sTargetValue);
}

/**//// <summary>
/// Constructor with known values
/// </summary>
/// <remarks>
/// The Assign Type is a string that identifies how to use the action.
/// </remarks>
/// <example>
/// Action oAction = new Action("object", "SomeExpression", "SomeExpression", ActionType.Var, "SomeID");<br/>
/// or<br/>
/// Action oAction = new Action("none", "SomeExpression", "SomeExpression, ActionType.Var, "SomeID");<br/>
/// </example>
/// <param name="sAssignType">The name of the type of this Action</param>
/// <param name="sTargetValue">expression to resolve compare to Rule Expression</param>
/// <param name="sAssignValue">expression to resolve and assign to variable (If ActionType is MakeVar or Var)</param>
/// <param name="oActionType">Type of Action to perform</param>
/// <param name="sID">Either the Variable ID or RuleID depending on the ActionType</param>
public Action(string sAssignType, string sTargetValue, string sAssignValue, ActionType oActionType, string sID)

{
m_sTargetStr = sTargetValue;
m_sAssignStr = sAssignValue;

m_oType = oActionType;
m_sID = sID;
m_oTargetVar = new Variable("var", sTargetValue);
this.m_oTargetVar.StringValue = "" + sTargetValue;

m_oAssignVar = new Variable("var", sAssignValue);
m_oAssignVar.StringValue = sAssignValue;
}

/**//// <summary>
/// Constructor with known values
/// </summary>
/// <remarks>
/// The Assign Type is a string that identifies how to use the action.
/// </remarks>
/// <example>
/// Action oAction = new Action("object", "SomeExpression", oSomeObject, ActionType.Var, "SomeID");<br/>
/// </example>
/// <param name="sAssignType"></param>
/// <param name="sTargetValue"></param>
/// <param name="oAssignValue">An Object that implements the IVariableObject Interface</param>
/// <param name="oActionType"></param>
/// <param name="sID"></param>
public Action(string sAssignType, string sTargetValue, IVariableObject oAssignValue, ActionType oActionType, string sID)

{
m_sTargetStr = sTargetValue;
if(sAssignType.ToLower().CompareTo("object") == 0)

{
m_oType = oActionType;
m_sID = sID;

m_oTargetVar = new Variable("var", sTargetValue);
this.m_oTargetVar.StringValue = "" + sTargetValue;

m_oAssignVar = new Variable("var", oAssignValue);
}
else

{
m_oType = oActionType;
m_sID = sID;
m_oTargetVar = new Variable("var", sTargetValue);
this.m_oTargetVar.StringValue = "" + sTargetValue;

m_oAssignVar = new Variable("var", "" + oAssignValue);
m_oAssignVar.StringValue = "" + oAssignValue;
}
}
#endregion


Publics#region Publics

/**//// <summary>
/// Determines if the Target value is equal to the Var value
/// </summary>
/// <remarks>
/// A Variable is used since the operators are overriden to provide type consistency
/// </remarks>
/// <param name="oVar">Encapulating variable to compare</param>
/// <returns>True if equal and False otherwise</returns>
public bool IsTargetMet(Variable oVar)

{
return m_oTargetVar == oVar;
}
#endregion

Developer#region Developer

/**//// <summary>
/// Returns the developer contact information
/// </summary>
/// <returns>The developer contact information</returns>
public string GetDeveloperInfo()

{
return "Jeff Bramlett, Jaxlab.com - jeff_bramlett@hotmail.com";
}
#endregion


}
与此相同的还有触发事件,变量的定义,并需要一个统一的接口对他们进行查找,调用,和卸载的操作管理,其中每个管理类实例中通过管理一组不重复的ID来映射出类实体。在此就不做详述了
引擎中最重要的部分我认为是解析器,也可以说是简单的语法分析方法集,在面向对象的设计思路下,就要将他设计成一个类,也就是实现消化输入信息的功能。将在下一篇文章中具体介绍
最近在研究业务架构的时候,偶尔发现了一个XML引擎驱动事件的程序,fm2.0的,倒不是为了报仇,只是我一直没弄明白到底xml配置是怎么驱动事件的。就看了看,多少有点收获,跟大家分享一下
工欲善其事,必先利其器,所以首先,要有个好的XML读写方法集,我看了看好多大家可能都用得上,就放在这了, 当个参考资料


































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































引擎中最重要的部分我认为是解析器,也可以说是简单的语法分析方法集,在面向对象的设计思路下,就要将他设计成一个类,也就是实现消化输入信息的功能。将在下一篇文章中具体介绍