Clone in a terminal:
SelectorPattern.vala
/*
	Copyright (C) 2016 Johan Mattsson

	This library is free software; you can redistribute it and/or modify 
	it under the terms of the GNU Lesser General Public License as 
	published by the Free Software Foundation; either version 3 of the 
	License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful, but 
	WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
	Lesser General Public License for more details.
*/

using B;
using Math;

namespace SvgBird {

/** A CSS selector pattern. */
public class SelectorPattern : GLib.Object {
	Gee.ArrayList<SelectorTag> tags = new Gee.ArrayList<SelectorTag> ();

	public SelectorPattern.empty () {
	}
	
	public SelectorPattern (string pattern) {
		string p = pattern.strip ();
		string[] elements = p.split (" ");

		foreach (string element in elements) {
			if (element != "") {
				tags.add (new SelectorTag (element));
			}
		}
	}

	public SelectorPattern get_id_patterns () {
		SelectorPattern pattern = new SelectorPattern.empty ();
		foreach (SelectorTag tag in tags) {
			if (tag.id != null) {
				pattern.tags.add (tag);
			}
		}
		return pattern;
	}

	public SelectorPattern get_class_patterns () {
		SelectorPattern pattern = new SelectorPattern.empty ();
		foreach (SelectorTag tag in tags) {
			if (tag.css_class != null) {
				pattern.tags.add (tag);
			}
		}
		return pattern;
	}

	public SelectorPattern get_pseudo_class_patterns () {
		SelectorPattern pattern = new SelectorPattern.empty ();
		foreach (SelectorTag tag in tags) {
			if (tag.pseudo_class != null) {
				pattern.tags.add (tag);
			}
		}
		return pattern;
	}

	public bool has_id () {
		foreach (SelectorTag tag in tags) {
			if (tag.id != null) {
				return true;
			}
		}
		
		return false;
	}

	public bool has_class () {
		foreach (SelectorTag tag in tags) {
			if (tag.css_class != null) {
				return true;
			}
		}
		
		return false;
	}

	public bool has_pseudo_class () {
		foreach (SelectorTag tag in tags) {
			if (tag.pseudo_class != null) {
				return true;
			}
		}
		
		return false;
	}
	
	public string to_string () {
		StringBuilder s = new StringBuilder ();
		
		foreach (SelectorTag tag in tags) {
			if (s.str != "") {
				s.append (" ");
			}
			
			s.append (tag.to_string ());
		}
		
		return s.str;
	}	
	public SelectorPattern copy () {
		SelectorPattern pattern = new SelectorPattern.empty ();
		
		foreach (SelectorTag tag in tags) {
			pattern.tags.add (tag.copy ());
		}
		
		return pattern;
	}
	
	public bool match (XmlElement tag, string? id, string? css_class, string? pseudo_class) {
		if (tag.get_name () == "") {
			return false;
		}
		
		for (int i = tags.size - 1; i >= 0; i--) {
			SelectorTag pattern = tags.get (i);
						
			if (pattern.name == ">") {
				if (i - 1 < 0) {
					return false;
				}
				
				string parent = tags.get (i - 1).name;
				
				if (!has_parent (tag, parent)) {
					return false;
				}
			} else if (pattern.name == "+") {
				if (i - 1 < 0) {
					return false;
				}
				
				string previous = tags.get (i - 1).name;
				
				if (!is_immediately_following (tag, previous)) {
					return false;
				}
			} 
			
			if (!pattern.match (tag, id, css_class, pseudo_class)) {
				return false;
			}
		}
		
		return true;
	}
	
	public bool is_immediately_following (XmlElement tag, string previous) {
		return get_previous (tag) == tag;
	}
	
	XmlElement? get_previous (XmlElement? xml_parent) {
		if (xml_parent == null) {
			return null;
		}
		
		XmlElement element = (!) xml_parent;
		XmlElement? parent = element.get_parent ();
		
		if (parent != null) {
			XmlElement? previous = null;
			
			foreach (XmlElement e in (!) parent) {
				if (e == xml_parent) {
					return previous;
				}
				
				previous = e;
			}
		}
		
		return null;
	}
	
	public bool has_parent (XmlElement tag, string parent) {
		XmlElement? xml_parent = tag.get_parent ();
		
		while (xml_parent != null) {
			if (((!) xml_parent).get_name () == parent) {
				return true;
			}
			
			xml_parent = tag.get_parent ();
		}
		
		return false;
	}
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112