Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Problems.GrammaticalOptimization/SharpVectorCss/Css/CssXPathSelector.cs @ 14128

Last change on this file since 14128 was 12762, checked in by aballeit, 9 years ago

#2283 GUI updates, Tree-chart, MCTS Version 2 (prune leaves)

File size: 12.7 KB
Line 
1using System;
2using System.Xml;
3using System.Xml.XPath;
4using System.Text;
5using System.Text.RegularExpressions;
6using System.Collections.Generic;
7
8namespace SharpVectors.Dom.Css
9{
10  #region Public enums
11  internal enum XPathSelectorStatus
12  {
13    Start, Parsed, Compiled, Error
14  }
15  #endregion
16
17  public sealed class CssXPathSelector
18  {
19    #region Static Fields
20
21    internal static Regex reSelector = new Regex(CssStyleRule.sSelector);
22   
23        #endregion
24
25        #region Internal Fields
26
27        internal XPathSelectorStatus Status = XPathSelectorStatus.Start;
28        internal string CssSelector;
29
30        #endregion
31
32        #region Private Fields
33
34        private int _specificity;
35        private string sXpath;
36        private XPathExpression xpath;
37        private IDictionary<string, string> _nsTable;
38
39        #endregion
40
41    #region Constructors and Destructor
42
43    public CssXPathSelector(string selector)
44            : this(selector, new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase))
45    {
46    }
47
48        public CssXPathSelector(string selector, IDictionary<string, string> namespaceTable)
49    {
50      CssSelector = selector.Trim();
51      _nsTable = namespaceTable;
52    }
53
54    #endregion
55
56    #region Public Properties
57
58    /// <summary>
59    /// Only used for testing!
60    /// </summary>
61    public string XPath
62    {
63      get
64      {
65        if (Status == XPathSelectorStatus.Start)
66        {
67          GetXPath(null);
68        }
69                return sXpath;
70      }
71    }
72
73    public int Specificity
74    {
75      get
76      {
77        if (Status == XPathSelectorStatus.Start)
78        {
79          GetXPath(null);
80        }
81        if (Status != XPathSelectorStatus.Error)
82                    return _specificity;
83        else
84                    return 0;
85      }
86    }
87
88    #endregion
89
90    #region Private Methods
91
92    private void AddSpecificity(int a, int b, int c)
93    {
94      _specificity += a*100 + b*10 + c;
95    }
96
97    private string NsToXPath(Match match)
98    {
99      string r = String.Empty;
100      Group g = match.Groups["ns"];
101
102      if (g != null && g.Success)
103      {
104        string prefix = g.Value.TrimEnd(new char[]{'|'});
105
106        if (prefix.Length == 0)
107        {
108          // a element in no namespace
109          r += "[namespace-uri()='']";
110        }
111        else if (prefix == "*")
112        {
113          // do nothing, any or no namespace is okey
114        }
115        else if (_nsTable.ContainsKey(prefix))
116        {
117          r += "[namespace-uri()='" + _nsTable[prefix] + "']";
118        }
119        else
120        {
121          //undeclared namespace => invalid CSS selector
122          r += "[false]";
123        }
124      }
125      else if (_nsTable.ContainsKey(String.Empty))
126      {
127        //if no default namespace has been specified, this is equivalent to *|E. Otherwise it is equivalent to ns|E where ns is the default namespace.
128
129        r += "[namespace-uri()='" + _nsTable[String.Empty] + "']";
130      }
131      return r;
132    }
133
134    private string TypeToXPath(Match match)
135    {
136      string r = String.Empty;
137      Group g = match.Groups["type"];
138      string s = g.Value;
139      if(!g.Success || s=="*") r = String.Empty;
140      else
141      {
142        r = "[local-name()='" + s + "']";
143        AddSpecificity(0, 0, 1);
144      }
145
146      return r;
147    }
148
149    private string ClassToXPath(Match match)
150    {
151      string r = String.Empty;
152      Group g = match.Groups["class"];
153
154      foreach(Capture c in g.Captures)
155      {
156        r += "[contains(concat(' ',@class,' '),' " + c.Value.Substring(1) + " ')]";
157        AddSpecificity(0, 1, 0);
158      }
159      return r;
160    }
161
162    private string IdToXPath(Match match)
163    {
164      string r = String.Empty;
165      Group g = match.Groups["id"];
166      if(g.Success)
167      {
168        // r = "[id('" + g.Value.Substring(1) + "')]";
169        r = "[@id='" + g.Value.Substring(1) + "']";
170        AddSpecificity(1, 0, 0);
171      }
172      return r;
173    }
174
175    private string GetAttributeMatch(string attSelector)
176    {
177      string fullAttName = attSelector.Trim();
178      int pipePos = fullAttName.IndexOf("|");
179      string attMatch = String.Empty;
180
181      if(pipePos == -1 || pipePos == 0)
182      {
183        // att or |att => should be in the undeclared namespace
184        string attName = fullAttName.Substring(pipePos+1);
185        attMatch = "@" + attName;
186      }
187      else if(fullAttName.StartsWith("*|"))
188      {
189        // *|att => in any namespace (undeclared or declared)
190        attMatch = "@*[local-name()='" + fullAttName.Substring(2) + "']";
191      }
192      else
193      {
194        // ns|att => must macht a declared namespace
195        string ns = fullAttName.Substring(0, pipePos);
196        string attName = fullAttName.Substring(pipePos+1);
197        if (_nsTable.ContainsKey(ns))
198        {
199          attMatch = "@" + ns + ":" + attName;
200        }
201        else
202        {
203          // undeclared namespace => selector should fail
204          attMatch = "false";
205        }
206      }
207      return attMatch;
208    }
209
210    private string PredicatesToXPath(Match match)
211    {
212      string r = String.Empty;
213      Group g = match.Groups["attributecheck"];
214     
215      foreach(Capture c in g.Captures)
216      {
217        r += "[" + GetAttributeMatch(c.Value) + "]";
218        AddSpecificity(0, 1, 0);
219      }
220
221      g = match.Groups["attributevaluecheck"];
222      Regex reAttributeValueCheck = new Regex("^" + CssStyleRule.attributeValueCheck + "?$");
223 
224
225      foreach(Capture c in g.Captures)
226      {
227        Match valueCheckMatch = reAttributeValueCheck.Match(c.Value);
228       
229        string attName = valueCheckMatch.Groups["attname"].Value;
230        string attMatch = GetAttributeMatch(attName);
231        string eq = valueCheckMatch.Groups["eqtype"].Value; // ~,^,$,*,|,nothing
232        string attValue = valueCheckMatch.Groups["attvalue"].Value;
233
234        switch(eq)
235        {
236          case "":
237            // [foo="bar"] => [@foo='bar']
238            r += "[" + attMatch + "='" + attValue + "']";
239            break;
240          case "~":
241            // [foo~="bar"]
242            // an E element whose "foo" attribute value is a list of space-separated values, one of which is exactly equal to "bar"
243            r += "[contains(concat(' '," + attMatch + ",' '),' " + attValue + " ')]";
244            break;
245          case "^":
246            // [foo^="bar"] 
247            // an E element whose "foo" attribute value begins exactly with the string "bar"
248            r += "[starts-with(" + attMatch + ",'" + attValue + "')]";
249            break;
250          case "$":
251            // [foo$="bar"] 
252            // an E element whose "foo" attribute value ends exactly with the string "bar"
253            int a = attValue.Length - 1;
254
255            r += "[substring(" + attMatch + ",string-length(" + attMatch + ")-" + a + ")='" + attValue + "']";
256            break;
257          case "*":
258            // [foo*="bar"] 
259            // an E element whose "foo" attribute value contains the substring "bar"
260            r += "[contains(" + attMatch + ",'" + attValue + "')]";
261            break;
262          case "|":
263            // [hreflang|="en"] 
264            // an E element whose "hreflang" attribute has a hyphen-separated list of values beginning (from the left) with "en"
265            r += "[" + attMatch + "='" + attValue + "' or starts-with(" + attMatch + ",'" + attValue + "-')]";
266            break;
267        }
268        AddSpecificity(0, 1, 0);
269      }
270
271      return r;
272    }
273
274    private string PseudoClassesToXPath(Match match, XPathNavigator nav)
275    {
276      int specificityA = 0;
277      int specificityB = 1;
278      int specificityC = 0;
279      string r = String.Empty;
280      Group g = match.Groups["pseudoclass"];
281
282      foreach(Capture c in g.Captures)
283      {
284        Regex reLang = new Regex(@"^lang\(([A-Za-z\-]+)\)$");
285        Regex reContains = new Regex("^contains\\((\"|\')?(?<stringvalue>.*?)(\"|\')?\\)$");
286
287        string s = @"^(?<type>(nth-child)|(nth-last-child)|(nth-of-type)|(nth-last-of-type))\(\s*";
288        s += @"(?<exp>(odd)|(even)|(((?<a>[\+-]?\d*)n)?(?<b>[\+-]?\d+)?))";
289        s += @"\s*\)$";
290        Regex reNth = new Regex(s);
291
292        string p = c.Value.Substring(1);
293
294        if(p == "root")
295        {
296          r += "[not(parent::*)]";
297        }
298        else if(p.StartsWith("not"))
299        {
300          string expr = p.Substring(4, p.Length-5);
301          CssXPathSelector sel = new CssXPathSelector(expr, _nsTable);
302
303          string xpath = sel.XPath;
304          if(xpath != null && xpath.Length>3)
305          {
306            // remove *[ and ending ]
307            xpath = xpath.Substring(2, xpath.Length-3);
308
309            r += "[not(" + xpath + ")]";
310
311            int specificity = sel.Specificity;
312
313            // specificity = 123
314            specificityA = (int)Math.Floor((double) specificity / 100);
315            specificity -= specificityA*100;
316            // specificity = 23
317            specificityB = (int)Math.Floor((double) (specificity) / 10);
318
319            specificity -= specificityB * 10;
320            // specificity = 3
321            specificityC = specificity;
322          }
323        }
324        else if(p == "first-child")
325        {
326          r += "[count(preceding-sibling::*)=0]";
327        }
328        else if(p == "last-child")
329        {
330          r += "[count(following-sibling::*)=0]";
331        }
332        else if(p == "only-child")
333        {
334          r += "[count(../*)=1]";
335        }
336        else if(p == "only-of-type")
337        {
338          r += "[false]";
339        }
340        else if(p == "empty")
341        {
342          r += "[not(child::*) and not(text())]";
343        }
344        else if(p == "target")
345        {
346          r += "[false]";
347        }
348        else if(p == "first-of-type")
349        {
350          r += "[false]";
351          //r += "[.=(../*[local-name='roffe'][position()=1])]";
352        }
353        else if(reLang.IsMatch(p))
354        {
355          r += "[lang('" + reLang.Match(p).Groups[1].Value + "')]";
356        }
357        else if(reContains.IsMatch(p))
358        {
359          r += "[contains(string(.),'" + reContains.Match(p).Groups["stringvalue"].Value + "')]";
360        }
361        else if(reNth.IsMatch(p))
362        {
363          Match m = reNth.Match(p);
364          string type = m.Groups["type"].Value;
365          string exp = m.Groups["exp"].Value;
366          int a = 0;
367          int b = 0;
368          if(exp == "odd")
369          {
370            a = 2;
371            b = 1;
372          }
373          else if(exp == "even")
374          {
375            a = 2;
376            b = 0;
377          }
378          else
379          {
380            string v = m.Groups["a"].Value;
381
382            if(v.Length == 0) a = 1;
383            else if(v.Equals("-")) a = -1;
384            else a = Int32.Parse(v);
385
386            if(m.Groups["b"].Success) b = Int32.Parse(m.Groups["b"].Value);
387          }
388
389
390          if(type.Equals("nth-child") || type.Equals("nth-last-child"))
391          {
392            string axis;
393            if(type.Equals("nth-child")) axis = "preceding-sibling";
394            else axis = "following-sibling";
395
396            if(a == 0)
397            {
398              r += "[count(" + axis + "::*)+1=" + b + "]";
399            }
400            else
401            {
402              r += "[((count(" + axis + "::*)+1-" + b + ") mod " + a + "=0)and((count(" + axis + "::*)+1-" + b + ") div " + a + ">=0)]";
403            }
404          }
405        }
406        AddSpecificity(specificityA, specificityB, specificityC);
407      }
408      return r;
409    }
410
411    private void SeperatorToXPath(Match match, StringBuilder xpath, string cur)
412    {
413      Group g = match.Groups["seperator"];
414      if(g.Success)
415      {
416        string s = g.Value.Trim();
417        if(s.Length == 0) cur += "//*";
418        else if(s == ">") cur += "/*";
419        else if(s == "+" || s == "~")
420        {
421          xpath.Append("[preceding-sibling::*");
422          if(s == "+")
423          {
424            xpath.Append("[position()=1]");
425          }
426          xpath.Append(cur);
427          xpath.Append("]");
428          cur = String.Empty;
429        }
430      }
431      xpath.Append(cur);
432    }
433
434    #endregion
435
436    #region Internal Methods
437
438    internal void GetXPath(XPathNavigator nav)
439    {
440      this._specificity = 0;
441      StringBuilder xpath = new StringBuilder("*");
442     
443      Match match = reSelector.Match(CssSelector);
444      while(match.Success)
445      {
446        if(match.Success && match.Value.Length > 0)
447        {
448          string x = String.Empty;
449          x += NsToXPath(match);
450          x += TypeToXPath(match);
451          x += ClassToXPath(match);
452          x += IdToXPath(match);
453          x += PredicatesToXPath(match);
454          x += PseudoClassesToXPath(match, nav);
455          SeperatorToXPath(match, xpath, x);
456         
457
458        }
459        match = match.NextMatch();
460      }
461      if(nav != null) Status = XPathSelectorStatus.Parsed;
462      sXpath = xpath.ToString();
463    }
464
465    private XmlNamespaceManager GetNSManager()
466    {
467      XmlNamespaceManager nsman = new XmlNamespaceManager(new NameTable());
468
469            foreach (KeyValuePair<string, string> dicEnum in _nsTable)
470            {
471                nsman.AddNamespace(dicEnum.Key, dicEnum.Value);
472            }
473            //IDictionaryEnumerator dicEnum = _nsTable.GetEnumerator();
474            //while(dicEnum.MoveNext())
475            //{
476            //    nsman.AddNamespace((string)dicEnum.Key, (string)dicEnum.Value);
477            //}
478
479      return nsman;
480
481    }
482
483    internal void Compile(XPathNavigator nav)
484    {
485      if(Status == XPathSelectorStatus.Start)
486      {
487        GetXPath(nav);
488      }
489      if(Status == XPathSelectorStatus.Parsed)
490      {
491        xpath = nav.Compile(sXpath);
492        xpath.SetContext(GetNSManager());
493       
494        Status = XPathSelectorStatus.Compiled;
495      }
496    }
497
498    public bool Matches(XPathNavigator nav)
499    {
500      if(Status != XPathSelectorStatus.Compiled)
501      {
502        Compile(nav);
503      }
504      if(Status == XPathSelectorStatus.Compiled)
505      {
506        try
507        {
508          return nav.Matches(xpath);
509        }
510        catch
511        {
512          return false;
513        }
514      }
515      else
516      {
517        return false;
518      }
519    }
520
521    #endregion
522  }
523}
Note: See TracBrowser for help on using the repository browser.