/// <summary> /// This constructor takes a Boolean expression in infix format. /// Some examples /// are shown below. Assume <b>T</b> is an instance of the /// <see cref="Tags"/> /// class. /// <list type="table"> /// <item> /// T[CommonPropertyNames.title] contains "foo" /// <term> /// </term> /// <description> /// <see cref="MediaComparer.IsMatch"/> will return /// true on media objects that have <i>foo</i> in the title. /// </description> /// </item> /// /// <item> /// T[CommonPropertyNames.creator] doesNotContain "\"HappyBand\"" /// <term> /// </term> /// <description> /// <see cref="MediaComparer.IsMatch"/> will return /// true on media objects that do not have <i>"HappyBand"</i> in the title. /// </description> /// </item> /// /// <item> /// (T[CommonPropertyNames.Class] = "object.item.audioItem.musicTrack" and Tags.PropertyAttributes.res_size > "40") or (T[CommonPropertyNames.author] exists true) /// <term> /// </term> /// <description> /// <see cref="MediaComparer.IsMatch"/> will return /// true on media objects that are music tracks with at least one resource greater than 40 bytes /// OR on media objects that have a value set for the author metadata. /// </description> /// </item> /// </list> /// </summary> /// <param name="infix">The boolean infix expression conforming to the syntax and semantics of ContentDirectory's boolean query language.</param> /// <exception cref="OpenSource.UPnP.AV.CdsMetadata.Error_MalformedSearchCriteria"> /// Thrown if the infix expression has a syntax error. /// </exception> public MediaComparer(string infix) { string allstring = infix.Trim(); if ((allstring == "") || (allstring == "*")) { this.m_AlwaysMatch = true; return; } //Initialize an empty stack and empty result string variable. // Stack stack = new Stack(); m_Postfix = new Queue(); RelExpStates state = RelExpStates.unknown; TokenResult token; while (infix.Length > 0) { infix = infix.Trim(); token = GetToken(ref infix, state); switch (state) { case RelExpStates.unknown: if (token.TokenType == Tokens.PropertyName) { state = RelExpStates.expectOp; } break; case RelExpStates.expectOp: if (token.TokenType != Tokens.Operator) { throw new UPnPCustomException(402, "Invalid Args: Invalid operator " + token.Data); } state = RelExpStates.expectValue; break; case RelExpStates.expectValue: if (token.TokenType != Tokens.PropertyValue) { throw new UPnPCustomException(402, "Invalid Args: Unexpected value " + token.Data); } state = RelExpStates.unknown; break; } switch (token.TokenType) { case Tokens.Operator: if (token.OpToken == OperatorTokens.LParen) { //left paren // stack.Push(token); } else if (token.OpToken == OperatorTokens.RParen) { //right paren // TokenResult tr = new TokenResult(false); do { if (stack.Count > 0) { tr = (TokenResult)stack.Pop(); if (tr.OpToken != OperatorTokens.LParen) { m_Postfix.Enqueue(tr); } } else { throw new UPnPCustomException(402, "Invalid Args: Missing Left Parenthesis."); } }while (tr.OpToken != OperatorTokens.LParen); } else { //standard operator // if (token.OpToken == OperatorTokens.Invalid) { throw new Exception("bad code"); } while ( (stack.Count > 0) && (((TokenResult)stack.Peek()).Precedence >= token.Precedence) && (((TokenResult)stack.Peek()).OpToken != OperatorTokens.LParen) ) { // While stack is not empty && // top operator has higher or equal precedence... // pop operator and stuff into queue m_Postfix.Enqueue(stack.Pop()); } stack.Push(token); } break; case Tokens.PropertyName: m_Postfix.Enqueue(token); TagExtractor te = new TagExtractor(token.Data); this.m_PE[token.Data] = te; break; case Tokens.PropertyValue: m_Postfix.Enqueue(token); break; } } // pop remaining items in stack and stuff into queue // while (stack.Count > 0) { TokenResult tr = (TokenResult)stack.Pop(); if (tr.OpToken != OperatorTokens.LParen) { m_Postfix.Enqueue(tr); } } }
/// <summary> /// Obtains the next token, given the current state and the expression. /// </summary> /// <param name="exp">the remaining portion of the infix expression to translate into a postfix expression</param> /// <param name="state">the state determines what types of tokens can be accepted.</param> /// <returns></returns> private TokenResult GetToken(ref string exp, RelExpStates state) { TokenResult token = new TokenResult(false); bool searchOps1 = false; bool searchOps2 = false; bool searchOps3 = false; bool parsePropName = false; bool parsePropValue = false; switch (state) { case RelExpStates.unknown: //look for everything searchOps1 = true; searchOps3 = true; parsePropName = true; break; case RelExpStates.expectOp: //look for relop, stringop, derived-op searchOps2 = true; break; case RelExpStates.expectValue: parsePropValue = true; break; } //remove any preceding whitespace chars exp = exp.Trim(); if (exp != "") { if ((searchOps1) && (token.TokenType == Tokens.Invalid)) { token = this.SeachOperators(ref exp, Operators1); } if ((searchOps2) && (token.TokenType == Tokens.Invalid)) { token = this.SeachOperators(ref exp, Operators2); } if ((searchOps3) && (token.TokenType == Tokens.Invalid)) { token = this.SeachOperators(ref exp, Operators3); } if (token.TokenType == Tokens.Invalid) { if (parsePropValue) { //it must be an operand... so parse the next whitespace delimited string or //extract the next string enclosed by quotes if ((exp.StartsWith("true")) || (exp.StartsWith("false"))) { // This is a value for an existence-op operation token = new TokenResult(exp, false); exp = exp.Substring(exp.Length); } else if (exp.StartsWith("\"")) { // This is a value operand that is delimited // by another double-quote int endQuote = 1; int escape = 0; bool endQuoteFound = false; // find the next end-quote that is not // an escaped end-quote. while (!endQuoteFound) { endQuote = exp.IndexOf("\"", endQuote); escape = exp.IndexOf("\\\"", escape); if ( (escape < 0) || (escape == endQuote - 1) || (escape > endQuote) ) { endQuoteFound = true; } } if (endQuote <= 0) { StringBuilder msg = new StringBuilder(exp.Length); msg.AppendFormat("Invalid Args: Unterminated end-quote in SearchCriteria, near \"{0}\"", exp); throw new UPnPCustomException(402, msg.ToString()); } string unescaped = exp.Substring(1, endQuote - 1); string escaped = unescaped.Replace("\\\\", "\\").Replace("\\\"", "\""); token = new TokenResult(escaped, false); exp = exp.Substring(endQuote + 1); } else { // Assume the CP provided a white-space delimited value without quotes //int endPos = exp.IndexOf(" "); int endPos = this.FindNextIndexOf(exp, WHITESPACESANDENDPAREN); string str = exp; if (endPos > 0) { str = exp.Substring(0, endPos); } token = new TokenResult(str, false); exp = exp.Substring(str.Length); } } else { // This is a property name, that is delimited by // a whitespace. //int endPos = exp.IndexOf(" "); int endPos = this.FindNextIndexOf(exp, WHITESPACES); if (endPos > 0) { string prop = exp.Substring(0, endPos); token = new TokenResult(prop, true); exp = exp.Substring(prop.Length); } else { throw new Error_MalformedSearchCriteria("Property name has not been properly delimited."); } } } } if ( (token.TokenType == Tokens.Invalid) ) { throw new UPnPCustomException(402, "Invalid Args: Invalid SearchCriteria string."); } return(token); }
/// <summary> /// Obtains the next token, given the current state and the expression. /// </summary> /// <param name="exp">the remaining portion of the infix expression to translate into a postfix expression</param> /// <param name="state">the state determines what types of tokens can be accepted.</param> /// <returns></returns> private TokenResult GetToken(ref string exp, RelExpStates state) { TokenResult token = new TokenResult(false); bool searchOps1 = false; bool searchOps2 = false; bool searchOps3 = false; bool parsePropName = false; bool parsePropValue = false; switch (state) { case RelExpStates.unknown: //look for everything searchOps1 = true; searchOps3 = true; parsePropName = true; break; case RelExpStates.expectOp: //look for relop, stringop, derived-op searchOps2 = true; break; case RelExpStates.expectValue: parsePropValue = true; break; } //remove any preceding whitespace chars exp = exp.Trim(); if (exp != "") { if ((searchOps1) && (token.TokenType == Tokens.Invalid)) { token = this.SeachOperators(ref exp, Operators1); } if ((searchOps2) && (token.TokenType == Tokens.Invalid)) { token = this.SeachOperators(ref exp, Operators2); } if ((searchOps3) && (token.TokenType == Tokens.Invalid)) { token = this.SeachOperators(ref exp, Operators3); } if (token.TokenType == Tokens.Invalid) { if (parsePropValue) { //it must be an operand... so parse the next whitespace delimited string or //extract the next string enclosed by quotes if ((exp.StartsWith("true")) || (exp.StartsWith("false"))) { // This is a value for an existence-op operation token = new TokenResult(exp, false); exp = exp.Substring(exp.Length); } else if (exp.StartsWith("\"")) { // This is a value operand that is delimited // by another double-quote int endQuote = 1; int escape = 0; bool endQuoteFound = false; // find the next end-quote that is not // an escaped end-quote. while (!endQuoteFound) { endQuote = exp.IndexOf("\"", endQuote); escape = exp.IndexOf("\\\"", escape); if ( (escape < 0) || (escape == endQuote - 1) || (escape > endQuote) ) { endQuoteFound = true; } } if (endQuote <= 0) { StringBuilder msg = new StringBuilder(exp.Length); msg.AppendFormat("Invalid Args: Unterminated end-quote in SearchCriteria, near \"{0}\"", exp); throw new UPnPCustomException(402, msg.ToString()); } string unescaped = exp.Substring(1, endQuote-1); string escaped = unescaped.Replace("\\\\", "\\").Replace("\\\"", "\""); token = new TokenResult(escaped, false); exp = exp.Substring(endQuote+1); } else { // Assume the CP provided a white-space delimited value without quotes //int endPos = exp.IndexOf(" "); int endPos = this.FindNextIndexOf(exp, WHITESPACESANDENDPAREN); string str = exp; if (endPos > 0) { str = exp.Substring(0, endPos); } token = new TokenResult(str, false); exp = exp.Substring(str.Length); } } else { // This is a property name, that is delimited by // a whitespace. //int endPos = exp.IndexOf(" "); int endPos = this.FindNextIndexOf(exp, WHITESPACES); if (endPos > 0) { string prop = exp.Substring(0, endPos); token = new TokenResult(prop, true); exp = exp.Substring(prop.Length); } else { throw new Error_MalformedSearchCriteria("Property name has not been properly delimited."); } } } } if ( (token.TokenType == Tokens.Invalid) ) { throw new UPnPCustomException(402, "Invalid Args: Invalid SearchCriteria string."); } return token; }