Example #1
0
void SelectorDataList::execute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
{
    if (!canUseFastQuery(rootNode)) {
        if (m_needsUpdatedDistribution)
            rootNode.updateDistribution();
        if (m_crossesTreeBoundary) {
            executeSlowTraversingShadowTree<SelectorQueryTrait>(rootNode, output);
        } else {
            executeSlow<SelectorQueryTrait>(rootNode, output);
        }
        return;
    }

    ASSERT(m_selectors.size() == 1);

    const CSSSelector& selector = *m_selectors[0];
    const CSSSelector& firstSelector = selector;

    // Fast path for querySelector*('#id'), querySelector*('tag#id').
    if (const CSSSelector* idSelector = selectorForIdLookup(firstSelector)) {
        const AtomicString& idToMatch = idSelector->value();
        if (rootNode.treeScope().containsMultipleElementsWithId(idToMatch)) {
            const WillBeHeapVector<RawPtrWillBeMember<Element>>& elements = rootNode.treeScope().getAllElementsById(idToMatch);
            size_t count = elements.size();
            for (size_t i = 0; i < count; ++i) {
                Element& element = *elements[i];
                if (!(isTreeScopeRoot(rootNode) || element.isDescendantOf(&rootNode)))
                    continue;
                if (selectorMatches(selector, element, rootNode)) {
                    SelectorQueryTrait::appendElement(output, element);
                    if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
                        return;
                }
            }
            return;
        }
        Element* element = rootNode.treeScope().getElementById(idToMatch);
        if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(&rootNode)))
            return;
        if (selectorMatches(selector, *element, rootNode))
            SelectorQueryTrait::appendElement(output, *element);
        return;
    }

    if (!firstSelector.tagHistory()) {
        // Fast path for querySelector*('.foo'), and querySelector*('div').
        switch (firstSelector.match()) {
        case CSSSelector::Class:
            collectElementsByClassName<SelectorQueryTrait>(rootNode, firstSelector.value(), output);
            return;
        case CSSSelector::Tag:
            collectElementsByTagName<SelectorQueryTrait>(rootNode, firstSelector.tagQName(), output);
            return;
        default:
            break; // If we need another fast path, add here.
        }
    }

    findTraverseRootsAndExecute<SelectorQueryTrait>(rootNode, output);
}
Example #2
0
ALWAYS_INLINE void SelectorDataList::executeFastPathForIdSelector(const Node* rootNode, const SelectorData& selectorData, const CSSSelector* idSelector, typename SelectorQueryTrait::OutputType& output) const
{
    ASSERT(m_selectors.size() == 1);
    ASSERT(idSelector);

    const AtomicString& idToMatch = idSelector->value();
    if (UNLIKELY(rootNode->treeScope()->containsMultipleElementsWithId(idToMatch))) {
        const Vector<Element*>* elements = rootNode->treeScope()->getAllElementsById(idToMatch);
        ASSERT(elements);
        size_t count = elements->size();
        bool rootNodeIsTreeScopeRoot = isTreeScopeRoot(rootNode);
        for (size_t i = 0; i < count; ++i) {
            Element* element = elements->at(i);
            if ((rootNodeIsTreeScopeRoot || element->isDescendantOf(rootNode)) && selectorMatches(selectorData, element, rootNode)) {
                SelectorQueryTrait::appendOutputForElement(output, element);
                if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
                    return;
            }
        }
        return;
    }

    Element* element = rootNode->treeScope()->getElementById(idToMatch);
    if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
        return;
    if (selectorMatches(selectorData, element, rootNode))
        SelectorQueryTrait::appendOutputForElement(output, element);
}
Example #3
0
ALWAYS_INLINE void SelectorDataList::executeFastPathForIdSelector(const Node* rootNode, const SelectorData& selectorData, const CSSSelector* idSelector, Vector<RefPtr<Node> >& matchedElements) const
{
    ASSERT(m_selectors.size() == 1);
    ASSERT(idSelector);
    const AtomicString& idToMatch = idSelector->value();
    if (UNLIKELY(rootNode->treeScope()->containsMultipleElementsWithId(idToMatch))) {
        const Vector<Element*>* elements = rootNode->treeScope()->getAllElementsById(idToMatch);
        ASSERT(elements);
        size_t count = elements->size();
        bool rootNodeIsTreeScopeRoot = isTreeScopeRoot(rootNode);
        for (size_t i = 0; i < count; ++i) {
            Element* element = elements->at(i);
            if ((rootNodeIsTreeScopeRoot || element->isDescendantOf(rootNode)) && selectorMatches(selectorData, element, rootNode)) {
                matchedElements.append(element);
                if (firstMatchOnly)
                    return;
            }
        }
        return;
    }
    Element* element = rootNode->treeScope()->getElementById(idToMatch);
    if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
        return;
    if (selectorMatches(selectorData, element, rootNode))
        matchedElements.append(element);
}
void SelectorDataList::execute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
{
    if (!canUseFastQuery(rootNode)) {
        executeSlow<SelectorQueryTrait>(rootNode, output);
        return;
    }

    ASSERT(m_selectors.size() == 1);

    const SelectorData& selector = m_selectors[0];
    const CSSSelector& firstSelector = selector.selector;

    // Fast path for querySelector*('#id'), querySelector*('tag#id').
    if (const CSSSelector* idSelector = selectorForIdLookup(firstSelector)) {
        const AtomicString& idToMatch = idSelector->value();
        Element* singleMatchingElement = 0;
        if (rootNode.treeScope().getNumberOfElementsWithId(idToMatch, singleMatchingElement) > 1) {
            const Vector<Element*>& elements = rootNode.treeScope().getAllElementsById(idToMatch);
            size_t count = elements.size();
            for (size_t i = 0; i < count; ++i) {
                Element& element = *elements[i];
                if (!(isTreeScopeRoot(rootNode) || element.isDescendantOf(&rootNode)))
                    continue;
                if (selectorMatches(selector, element, rootNode)) {
                    SelectorQueryTrait::appendElement(output, element);
                    if (SelectorQueryTrait::shouldOnlyMatchFirstElement)
                        return;
                }
            }
            return;
        }

        if (!singleMatchingElement || !(isTreeScopeRoot(rootNode) || singleMatchingElement->isDescendantOf(&rootNode)))
            return;
        if (selectorMatches(selector, *singleMatchingElement, rootNode))
            SelectorQueryTrait::appendElement(output, *singleMatchingElement);
        return;
    }

    if (!firstSelector.tagHistory()) {
        // Fast path for querySelector*('.foo'), and querySelector*('div').
        switch (firstSelector.m_match) {
        case CSSSelector::Class:
            collectElementsByClassName<SelectorQueryTrait>(rootNode, firstSelector.value(), output);
            return;
        case CSSSelector::Tag:
            collectElementsByTagName<SelectorQueryTrait>(rootNode, firstSelector.tagQName(), output);
            return;
        default:
            break; // If we need another fast path, add here.
        }
    }

    findTraverseRootsAndExecute<SelectorQueryTrait>(rootNode, output);
}
void SelectorDataList::findTraverseRootsAndExecute(ContainerNode& rootNode, typename SelectorQueryTrait::OutputType& output) const
{
    // We need to return the matches in document order. To use id lookup while there is possiblity of multiple matches
    // we would need to sort the results. For now, just traverse the document in that case.
    ASSERT(m_selectors.size() == 1);

    bool isRightmostSelector = true;
    bool startFromParent = false;
    Element* singleMatchingElement = 0;

    for (const CSSSelector* selector = &m_selectors[0].selector; selector; selector = selector->tagHistory()) {
        if (selector->m_match == CSSSelector::Id && (rootNode.document().getNumberOfElementsWithId(selector->value(), singleMatchingElement) <= 1)) {
            ContainerNode* adjustedNode = &rootNode;
            if (singleMatchingElement && (isTreeScopeRoot(rootNode) || singleMatchingElement->isDescendantOf(&rootNode)))
                adjustedNode = singleMatchingElement;
            else if (!singleMatchingElement || isRightmostSelector)
                adjustedNode = 0;
            if (isRightmostSelector) {
                executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], adjustedNode, MatchesTraverseRoots, rootNode, output);
                return;
            }

            if (startFromParent && adjustedNode)
                adjustedNode = adjustedNode->parentNode();

            executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], adjustedNode, DoesNotMatchTraverseRoots, rootNode, output);
            return;
        }

        // If we have both CSSSelector::Id and CSSSelector::Class at the same time, we should use Id
        // to find traverse root.
        if (!SelectorQueryTrait::shouldOnlyMatchFirstElement && !startFromParent && selector->m_match == CSSSelector::Class) {
            if (isRightmostSelector) {
                ClassElementList<AllElements> traverseRoots(rootNode, selector->value());
                executeForTraverseRoots<SelectorQueryTrait>(m_selectors[0], traverseRoots, MatchesTraverseRoots, rootNode, output);
                return;
            }
            // Since there exists some ancestor element which has the class name, we need to see all children of rootNode.
            if (ancestorHasClassName(rootNode, selector->value())) {
                executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], &rootNode, DoesNotMatchTraverseRoots, rootNode, output);
                return;
            }

            ClassElementList<OnlyRoots> traverseRoots(rootNode, selector->value());
            executeForTraverseRoots<SelectorQueryTrait>(m_selectors[0], traverseRoots, DoesNotMatchTraverseRoots, rootNode, output);
            return;
        }

        if (selector->relation() == CSSSelector::SubSelector)
            continue;
        isRightmostSelector = false;
        if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
            startFromParent = true;
        else
            startFromParent = false;
    }

    executeForTraverseRoot<SelectorQueryTrait>(m_selectors[0], &rootNode, DoesNotMatchTraverseRoots, rootNode, output);
}
void SelectorDataList::executeQueryAll(Node* rootNode, Vector<RefPtr<Node> >& matchedElements) const
{
    if (!canUseFastQuery(rootNode))
        return executeSlowQueryAll(rootNode, matchedElements);

    ASSERT(m_selectors.size() == 1);
    ASSERT(m_selectors[0].selector);

    const CSSSelector* firstSelector = m_selectors[0].selector;

    if (!firstSelector->tagHistory()) {
        // Fast path for querySelectorAll('#id'), querySelectorAl('.foo'), and querySelectorAll('div').
        switch (firstSelector->m_match) {
        case CSSSelector::Id:
            {
                if (rootNode->document()->containsMultipleElementsWithId(firstSelector->value()))
                    break;

                // Just the same as getElementById.
                Element* element = rootNode->treeScope()->getElementById(firstSelector->value());
                if (element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
                    matchedElements.append(element);
                return;
            }
        case CSSSelector::Class:
            return collectElementsByClassName(rootNode, firstSelector->value(), matchedElements);
        case CSSSelector::Tag:
            return collectElementsByTagName(rootNode, firstSelector->tagQName(), matchedElements);
        default:
            break; // If we need another fast path, add here.
        }
    }

    bool matchTraverseRoots;
    OwnPtr<SimpleNodeList> traverseRoots = findTraverseRoots(rootNode, matchTraverseRoots);
    if (traverseRoots->isEmpty())
        return;

    const SelectorData& selector = m_selectors[0];
    if (matchTraverseRoots) {
        while (!traverseRoots->isEmpty()) {
            Node* node = traverseRoots->next();
            Element* element = toElement(node);
            if (selectorMatches(selector, element, rootNode))
                matchedElements.append(element);
        }
        return;
    }

    while (!traverseRoots->isEmpty()) {
        Node* traverseRoot = traverseRoots->next();
        for (Element* element = ElementTraversal::firstWithin(traverseRoot); element; element = ElementTraversal::next(element, traverseRoot)) {
            if (selectorMatches(selector, element, rootNode))
                matchedElements.append(element);
        }
    }
}
// If returns true, traversalRoots has the elements that may match the selector query.
//
// If returns false, traversalRoots has the rootNode parameter or descendants of rootNode representing
// the subtree for which we can limit the querySelector traversal.
//
// The travseralRoots may be empty, regardless of the returned bool value, if this method finds that the selectors won't
// match any element.
PassOwnPtr<SimpleNodeList> SelectorDataList::findTraverseRoots(Node* rootNode, bool& matchTraverseRoots) const
{
    // We need to return the matches in document order. To use id lookup while there is possiblity of multiple matches
    // we would need to sort the results. For now, just traverse the document in that case.
    ASSERT(rootNode);
    ASSERT(m_selectors.size() == 1);
    ASSERT(m_selectors[0].selector);

    bool isRightmostSelector = true;
    bool startFromParent = false;

    for (const CSSSelector* selector = m_selectors[0].selector; selector; selector = selector->tagHistory()) {
        if (selector->m_match == CSSSelector::Id && !rootNode->document().containsMultipleElementsWithId(selector->value())) {
            Element* element = rootNode->treeScope().getElementById(selector->value());
            if (element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
                rootNode = element;
            else if (!element || isRightmostSelector)
                rootNode = 0;
            if (isRightmostSelector) {
                matchTraverseRoots = true;
                return adoptPtr(new SingleNodeList(rootNode));
            }
            if (startFromParent && rootNode)
                rootNode = rootNode->parentNode();

            matchTraverseRoots = false;
            return adoptPtr(new SingleNodeList(rootNode));
        }

        // If we have both CSSSelector::Id and CSSSelector::Class at the same time, we should use Id
        // to find traverse root.
        if (!startFromParent && selector->m_match == CSSSelector::Class) {
            if (isRightmostSelector) {
                matchTraverseRoots = true;
                return adoptPtr(new ClassElementList(rootNode, selector->value()));
            }
            matchTraverseRoots = false;
            // Since there exists some ancestor element which has the class name, we need to see all children of rootNode.
            if (ancestorHasClassName(rootNode, selector->value()))
                return adoptPtr(new SingleNodeList(rootNode));

            return adoptPtr(new ClassRootNodeList(rootNode, selector->value()));
        }

        if (selector->relation() == CSSSelector::SubSelector)
            continue;
        isRightmostSelector = false;
        if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
            startFromParent = true;
        else
            startFromParent = false;
    }

    matchTraverseRoots = false;
    return adoptPtr(new SingleNodeList(rootNode));
}
void StyleSheetScopingNodeList::remove(ContainerNode* node)
{
    if (isTreeScopeRoot(node) || !m_scopingNodes)
        return;

    m_scopingNodes->remove(node);
    if (node->inDocument() && node->numberOfScopedHTMLStyleChildren())
        return;

    if (!m_scopingNodesRemoved)
        m_scopingNodesRemoved = adoptPtr(new ListHashSet<Node*, 4>());
    m_scopingNodesRemoved->add(node);
}
void StyleSheetScopingNodeList::add(ContainerNode* node)
{
    ASSERT(node && node->inDocument());
    if (isTreeScopeRoot(node))
        return;

    if (!m_scopingNodes)
        m_scopingNodes = adoptPtr(new DocumentOrderedList());
    m_scopingNodes->add(node);

    if (m_scopingNodesRemoved)
        m_scopingNodesRemoved->remove(node);
}
void SelectorDataList::execute(Node* rootNode, Vector<RefPtr<Node> >& matchedElements) const
{
    if (const CSSSelector* idSelector = selectorForIdLookup(rootNode)) {
        ASSERT(m_selectors.size() == 1);
        const AtomicString& idToMatch = idSelector->value();
        if (UNLIKELY(rootNode->treeScope()->containsMultipleElementsWithId(idToMatch))) {
            const Vector<Element*>* elements = rootNode->treeScope()->getAllElementsById(idToMatch);
            ASSERT(elements);
            size_t count = elements->size();
            for (size_t i = 0; i < count; ++i) {
                Element* element = elements->at(i);
                if (selectorMatches(m_selectors[0], element, rootNode)) {
                    matchedElements.append(element);
                    if (firstMatchOnly)
                        return;
                }
            }
            return;
        }
        Element* element = rootNode->treeScope()->getElementById(idToMatch);
        if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
            return;
        if (selectorMatches(m_selectors[0], element, rootNode))
            matchedElements.append(element);
        return;
    }

    unsigned selectorCount = m_selectors.size();
    if (selectorCount == 1) {
        const SelectorData& selector = m_selectors[0];
        for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(element, rootNode)) {
            if (selectorMatches(selector, element, rootNode)) {
                matchedElements.append(element);
                if (firstMatchOnly)
                    return;
            }
        }
        return;
    }
    for (Element* element = ElementTraversal::firstWithin(rootNode); element; element = ElementTraversal::next(element, rootNode)) {
        for (unsigned i = 0; i < selectorCount; ++i) {
            if (selectorMatches(m_selectors[i], element, rootNode)) {
                matchedElements.append(element);
                if (firstMatchOnly)
                    return;
                break;
            }
        }
    }
}
void StyleSheetScopingNodeList::remove(ContainerNode* node)
{
    if (isTreeScopeRoot(node) || !m_scopingNodes)
        return;

    // If the node is still working as a scoping node, we cannot remove.
    if (node->inDocument())
        return;

    m_scopingNodes->remove(node);
    if (!m_scopingNodesRemoved)
        m_scopingNodesRemoved = adoptPtr(new ListHashSet<Node*, 4>());
    m_scopingNodesRemoved->add(node);
}
Element* SelectorDataList::executeQueryFirst(Node* rootNode) const
{
    if (!canUseFastQuery(rootNode))
        return executeSlowQueryFirst(rootNode);


    const CSSSelector* selector = m_selectors[0].selector;
    ASSERT(selector);

    if (!selector->tagHistory()) {
        // Fast path for querySelector('#id'), querySelector('.foo'), and querySelector('div').
        // Many web developers uses querySelector with these simple selectors.
        switch (selector->m_match) {
        case CSSSelector::Id:
            {
                if (rootNode->document()->containsMultipleElementsWithId(selector->value()))
                    break;
                Element* element = rootNode->treeScope()->getElementById(selector->value());
                return element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)) ? element : 0;
            }
        case CSSSelector::Class:
            return findElementByClassName(rootNode, selector->value());
        case CSSSelector::Tag:
            return findElementByTagName(rootNode, selector->tagQName());
        default:
            break; // If we need another fast path, add here.
        }
    }

    bool matchTraverseRoot;
    Node* traverseRootNode = findTraverseRoot(rootNode, matchTraverseRoot);
    if (!traverseRootNode)
        return 0;
    if (matchTraverseRoot) {
        ASSERT(m_selectors.size() == 1);
        ASSERT(traverseRootNode->isElementNode());
        Element* element = toElement(traverseRootNode);
        return selectorMatches(m_selectors[0], element, rootNode) ? element : 0;
    }

    for (Element* element = ElementTraversal::firstWithin(traverseRootNode); element; element = ElementTraversal::next(element, traverseRootNode)) {
        if (selectorMatches(m_selectors[0], element, rootNode))
            return element;
    }
    return 0;
}
Example #13
0
void SelectorDataList::execute(Node* rootNode, Vector<RefPtr<Node> >& matchedElements) const
{
    SelectorChecker selectorChecker(rootNode->document(), SelectorChecker::QueryingRules);

    if (canUseIdLookup(rootNode)) {
        ASSERT(m_selectors.size() == 1);
        const CSSSelector* selector = m_selectors[0].selector;
        Element* element = rootNode->treeScope()->getElementById(selector->value());
        if (!element || !(isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
            return;
        if (selectorChecker.matches(m_selectors[0].selector, element, m_selectors[0].isFastCheckable))
            matchedElements.append(element);
        return;
    }

    unsigned selectorCount = m_selectors.size();

    Node* n = rootNode->firstChild();
    while (n) {
        if (n->isElementNode()) {
            Element* element = static_cast<Element*>(n);
            for (unsigned i = 0; i < selectorCount; ++i) {
                if (selectorChecker.matches(m_selectors[i].selector, element, m_selectors[i].isFastCheckable)) {
                    matchedElements.append(element);
                    if (firstMatchOnly)
                        return;
                    break;
                }
            }
            if (element->firstChild()) {
                n = element->firstChild();
                continue;
            }
        }
        while (!n->nextSibling()) {
            n = n->parentNode();
            if (n == rootNode)
                return;
        }
        n = n->nextSibling();
    }
}
Example #14
0
static ContainerNode& filterRootById(ContainerNode& rootNode, const CSSSelector& firstSelector)
{
    if (!rootNode.inDocument())
        return rootNode;
    if (rootNode.document().inQuirksMode())
        return rootNode;

    // If there was an Id match in the rightmost Simple Selector, we should be in a RightMostWithIdMatch, not in filter.
    // Thus we can skip the rightmost match.
    const CSSSelector* selector = &firstSelector;
    do {
        ASSERT(!canBeUsedForIdFastPath(*selector));
        if (selector->relation() != CSSSelector::SubSelector)
            break;
        selector = selector->tagHistory();
    } while (selector);

    bool inAdjacentChain = false;
    for (; selector; selector = selector->tagHistory()) {
        if (canBeUsedForIdFastPath(*selector)) {
            const AtomicString& idToMatch = selector->value();
            if (ContainerNode* searchRoot = rootNode.treeScope().getElementById(idToMatch)) {
                if (LIKELY(!rootNode.treeScope().containsMultipleElementsWithId(idToMatch))) {
                    if (inAdjacentChain)
                        searchRoot = searchRoot->parentNode();
                    if (searchRoot && (isTreeScopeRoot(rootNode) || searchRoot == &rootNode || searchRoot->isDescendantOf(&rootNode)))
                        return *searchRoot;
                }
            }
        }
        if (selector->relation() == CSSSelector::SubSelector)
            continue;
        if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
            inAdjacentChain = true;
        else
            inAdjacentChain = false;
    }
    return rootNode;
}
// If matchTraverseRoot is true, the returned Node is the single Element that may match the selector query.
//
// If matchTraverseRoot is false, the returned Node is the rootNode parameter or a descendant of rootNode representing
// the subtree for which we can limit the querySelector traversal.
//
// The returned Node may be 0, regardless of matchTraverseRoot, if this method finds that the selectors won't
// match any element.
Node* SelectorDataList::findTraverseRoot(Node* rootNode, bool& matchTraverseRoot) const
{
    // We need to return the matches in document order. To use id lookup while there is possiblity of multiple matches
    // we would need to sort the results. For now, just traverse the document in that case.
    ASSERT(rootNode);
    ASSERT(m_selectors.size() == 1);
    ASSERT(m_selectors[0].selector);

    bool matchSingleNode = true;
    bool startFromParent = false;
    for (const CSSSelector* selector = m_selectors[0].selector; selector; selector = selector->tagHistory()) {
        if (selector->m_match == CSSSelector::Id && !rootNode->document()->containsMultipleElementsWithId(selector->value())) {
            Element* element = rootNode->treeScope()->getElementById(selector->value());
            if (element && (isTreeScopeRoot(rootNode) || element->isDescendantOf(rootNode)))
                rootNode = element;
            else if (!element || matchSingleNode)
                rootNode = 0;
            if (matchSingleNode) {
                matchTraverseRoot = true;
                return rootNode;
            }
            if (startFromParent && rootNode)
                rootNode = rootNode->parentNode();
            matchTraverseRoot = false;
            return rootNode;
        }
        if (selector->relation() == CSSSelector::SubSelector)
            continue;
        matchSingleNode = false;
        if (selector->relation() == CSSSelector::DirectAdjacent || selector->relation() == CSSSelector::IndirectAdjacent)
            startFromParent = true;
        else
            startFromParent = false;
    }
    matchTraverseRoot = false;
    return rootNode;
}