public static IList <ICssSelectorItem> CreateCssSelector(String selector) { IList <ICssSelectorItem> cssSelectorItems = new List <ICssSelectorItem>(); Match itemMatcher = selectorPattern.Match(selector); bool isTagSelector = false; int crc = 0; while (itemMatcher.Success) { String selectorItem = itemMatcher.Groups[0].Value; crc += selectorItem.Length; switch (selectorItem[0]) { case '#': cssSelectorItems.Add(new CssIdSelector(selectorItem.Substring(1))); break; case '.': cssSelectorItems.Add(new CssClassSelector(selectorItem.Substring(1))); break; case '[': cssSelectorItems.Add(new CssAttributeSelector(selectorItem)); break; case ':': cssSelectorItems.Add(new CssPseudoSelector(selectorItem)); break; case ' ': case '+': case '>': case '~': if (cssSelectorItems.Count == 0) { return(null); } ICssSelectorItem lastItem = cssSelectorItems[cssSelectorItems.Count - 1]; ICssSelectorItem currItem = new CssSeparatorSelector(selectorItem[0]); if (lastItem is CssSeparatorSelector) { if (selectorItem[0] == ' ') { break; } else if (lastItem.Separator == ' ') { cssSelectorItems[cssSelectorItems.Count - 1] = currItem; } else { return(null); } } else { cssSelectorItems.Add(currItem); isTagSelector = false; } break; default: //and case '*': if (isTagSelector) { return(null); } isTagSelector = true; cssSelectorItems.Add(new CssTagSelector(selectorItem)); break; } itemMatcher = itemMatcher.NextMatch(); } if (selector.Length == 0 || selector.Length != crc) { return(null); } return(cssSelectorItems); }
/// <summary>Parses the selector items.</summary> /// <param name="selector"> /// the selectors in the form of a /// <see cref="System.String"/> /// </param> /// <returns> /// the resulting list of /// <see cref="iText.StyledXmlParser.Css.Selector.Item.ICssSelectorItem"/> /// </returns> public static IList <ICssSelectorItem> ParseSelectorItems(String selector) { IList <ICssSelectorItem> selectorItems = new List <ICssSelectorItem>(); CssSelectorParserMatch match = new CssSelectorParserMatch(selector, selectorPattern); bool tagSelectorDescription = false; while (match.Success()) { String selectorItem = match.GetValue(); char firstChar = selectorItem[0]; switch (firstChar) { case '#': { match.Next(); selectorItems.Add(new CssIdSelectorItem(selectorItem.Substring(1))); break; } case '.': { match.Next(); selectorItems.Add(new CssClassSelectorItem(selectorItem.Substring(1))); break; } case '[': { match.Next(); selectorItems.Add(new CssAttributeSelectorItem(selectorItem)); break; } case ':': { AppendPseudoSelector(selectorItems, selectorItem, match); break; } case ' ': case '+': case '>': case '~': { match.Next(); if (selectorItems.Count == 0) { throw new ArgumentException(MessageFormatUtil.Format("Invalid token detected in the start of the selector string: {0}" , firstChar)); } ICssSelectorItem lastItem = selectorItems[selectorItems.Count - 1]; CssSeparatorSelectorItem curItem = new CssSeparatorSelectorItem(firstChar); if (lastItem is CssSeparatorSelectorItem) { if (curItem.GetSeparator() == ' ') { break; } else { if (((CssSeparatorSelectorItem)lastItem).GetSeparator() == ' ') { selectorItems[selectorItems.Count - 1] = curItem; } else { throw new ArgumentException(MessageFormatUtil.Format("Invalid selector description. Two consequent characters occurred: {0}, {1}" , ((CssSeparatorSelectorItem)lastItem).GetSeparator(), curItem.GetSeparator())); } } } else { selectorItems.Add(curItem); tagSelectorDescription = false; } break; } default: { //and case '*': match.Next(); if (tagSelectorDescription) { throw new InvalidOperationException("Invalid selector string"); } tagSelectorDescription = true; selectorItems.Add(new CssTagSelectorItem(selectorItem)); break; } } } if (selectorItems.Count == 0) { throw new ArgumentException("Selector declaration is invalid"); } return(selectorItems); }
/// <summary>Checks if a node matches the selector.</summary> /// <param name="element">the node</param> /// <param name="lastSelectorItemInd">the index of the last selector</param> /// <returns>true, if there's a match</returns> private bool Matches(INode element, int lastSelectorItemInd) { if (!(element is IElementNode)) { return(false); } if (lastSelectorItemInd < 0) { return(true); } //TODO: Consider pseudo-elements in SVG bool isPseudoElement = element is CssPseudoElementNode; for (int i = lastSelectorItemInd; i >= 0; i--) { if (isPseudoElement && selectorItems[lastSelectorItemInd] is CssPseudoElementSelectorItem && i < lastSelectorItemInd ) { // Pseudo element selector item shall be at the end of the selector string // and be single pseudo element selector item in it. All other selector items are checked against // pseudo element node parent. element = element.ParentNode(); isPseudoElement = false; } ICssSelectorItem currentItem = selectorItems[i]; if (currentItem is CssSeparatorSelectorItem) { char separator = ((CssSeparatorSelectorItem)currentItem).GetSeparator(); switch (separator) { case '>': { return(Matches(element.ParentNode(), i - 1)); } case ' ': { INode parent = element.ParentNode(); while (parent != null) { bool parentMatches = Matches(parent, i - 1); if (parentMatches) { return(true); } else { parent = parent.ParentNode(); } } return(false); } case '~': { INode parent = element.ParentNode(); if (parent != null) { int indexOfElement = parent.ChildNodes().IndexOf(element); for (int j = indexOfElement - 1; j >= 0; j--) { if (Matches(parent.ChildNodes()[j], i - 1)) { return(true); } } } return(false); } case '+': { INode parent = element.ParentNode(); if (parent != null) { int indexOfElement = parent.ChildNodes().IndexOf(element); INode previousElement = null; for (int j = indexOfElement - 1; j >= 0; j--) { if (parent.ChildNodes()[j] is IElementNode) { previousElement = parent.ChildNodes()[j]; break; } } if (previousElement != null) { return(indexOfElement > 0 && Matches(previousElement, i - 1)); } } return(false); } default: { return(false); } } } else { if (!currentItem.Matches(element)) { return(false); } } } return(true); }
/// <summary> /// Resolves a pseudo selector, appends it to list and updates /// <see cref="CssSelectorParserMatch"/> /// in process. /// </summary> /// <param name="selectorItems">list of items to which new selector will be added to</param> /// <param name="pseudoSelector">the pseudo selector</param> /// <param name="match"> /// the corresponding /// <see cref="CssSelectorParserMatch"/> /// that will be updated. /// </param> private static void AppendPseudoSelector(IList <ICssSelectorItem> selectorItems, String pseudoSelector, CssSelectorParserMatch match) { pseudoSelector = pseudoSelector.ToLowerInvariant(); int start = match.GetIndex() + pseudoSelector.Length; String source = match.GetSource(); if (start < source.Length && source[start] == '(') { int bracketDepth = 1; int curr = start + 1; while (bracketDepth > 0 && curr < source.Length) { if (source[curr] == '(') { ++bracketDepth; } else { if (source[curr] == ')') { --bracketDepth; } else { if (source[curr] == '"' || source[curr] == '\'') { curr = CssUtils.FindNextUnescapedChar(source, source[curr], curr + 1); } } } ++curr; } if (bracketDepth == 0) { match.Next(curr); pseudoSelector += source.JSubstring(start, curr); } else { match.Next(); } } else { match.Next(); } /* * This :: notation is introduced by the current document in order to establish a discrimination between * pseudo-classes and pseudo-elements. * For compatibility with existing style sheets, user agents must also accept the previous one-colon * notation for pseudo-elements introduced in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and :after). * This compatibility is not allowed for the new pseudo-elements introduced in this specification. */ if (pseudoSelector.StartsWith("::")) { selectorItems.Add(new CssPseudoElementSelectorItem(pseudoSelector.Substring(2))); } else { if (pseudoSelector.StartsWith(":") && legacyPseudoElements.Contains(pseudoSelector.Substring(1))) { selectorItems.Add(new CssPseudoElementSelectorItem(pseudoSelector.Substring(1))); } else { ICssSelectorItem pseudoClassSelectorItem = CssPseudoClassSelectorItem.Create(pseudoSelector.Substring(1)); if (pseudoClassSelectorItem == null) { throw new ArgumentException(MessageFormatUtil.Format(iText.StyledXmlParser.LogMessageConstant.UNSUPPORTED_PSEUDO_CSS_SELECTOR , pseudoSelector)); } selectorItems.Add(pseudoClassSelectorItem); } } }