/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * XSEC
 *
 * TXFMXPathFilter := Class that performs XPath transforms
 *
 * $Id: TXFMXPathFilter.cpp 1833341 2018-06-11 16:25:41Z scantor $
 *
 */


#include <xsec/dsig/DSIGConstants.hpp>
#include <xsec/dsig/DSIGXPathFilterExpr.hpp>
#include <xsec/dsig/DSIGXPathHere.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/transformers/TXFMXPathFilter.hpp>
#include <xsec/transformers/TXFMParser.hpp>

XERCES_CPP_NAMESPACE_USE

#ifdef XSEC_HAVE_XALAN

#include "../utils/XSECDOMUtils.hpp"

#include <xercesc/util/Janitor.hpp>

#if defined(_MSC_VER)
#    pragma warning(disable: 4267)
#endif

#include <xalanc/XalanDOM/XalanDocument.hpp>
#include <xalanc/XalanDOM/XalanDOMString.hpp>
#include <xalanc/XercesParserLiaison/XercesDocumentWrapper.hpp>
#include <xalanc/XercesParserLiaison/XercesDOMSupport.hpp>
#include <xalanc/XercesParserLiaison/XercesParserLiaison.hpp>
#include <xalanc/XPath/XPathEvaluator.hpp>
#include <xalanc/XPath/XPathProcessorImpl.hpp>
#include <xalanc/XPath/XPathFactoryDefault.hpp>
#include <xalanc/XPath/NodeRefList.hpp>
#include <xalanc/XPath/XPathEnvSupportDefault.hpp>
#include <xalanc/XPath/XPathConstructionContextDefault.hpp>
#include <xalanc/XPath/ElementPrefixResolverProxy.hpp>
#include <xalanc/XPath/XObjectFactoryDefault.hpp>
#include <xalanc/XPath/XPathExecutionContextDefault.hpp>
#include <xalanc/XSLT/XSLTResultTarget.hpp>

#if defined(_MSC_VER)
#    pragma warning(default: 4267)
#endif

// Xalan namespace usage
XALAN_USING_XALAN(XPathProcessorImpl)
XALAN_USING_XALAN(XalanDOMString)
XALAN_USING_XALAN(XercesDOMSupport)
XALAN_USING_XALAN(XercesParserLiaison)
XALAN_USING_XALAN(XercesDocumentWrapper)
XALAN_USING_XALAN(XercesWrapperNavigator)
XALAN_USING_XALAN(XPathEvaluator)
XALAN_USING_XALAN(XPathFactoryDefault)
XALAN_USING_XALAN(XPathConstructionContextDefault)
XALAN_USING_XALAN(XalanDocument)
XALAN_USING_XALAN(XalanNode)
XALAN_USING_XALAN(XalanDOMChar)
XALAN_USING_XALAN(XPathEnvSupportDefault)
XALAN_USING_XALAN(XObjectFactoryDefault)
XALAN_USING_XALAN(XObjectPtr)
XALAN_USING_XALAN(XPathExecutionContextDefault)
XALAN_USING_XALAN(ElementPrefixResolverProxy)
XALAN_USING_XALAN(XPath)
XALAN_USING_XALAN(NodeRefListBase)
XALAN_USING_XALAN(XSLTResultTarget)
XALAN_USING_XALAN(XSLException)

#endif

#ifdef XSEC_HAVE_XPATH

#include <iostream>

#define KLUDGE_PREFIX "berindsig"

// Helper functions - come from DSIGXPath

void setXPathNS(DOMDocument* d,
                DOMNamedNodeMap* xAtts,
                XSECXPathNodeList& addedNodes,
                XSECSafeBufferFormatter* formatter,
                XSECNameSpaceExpander* nse);

void clearXPathNS(DOMDocument*d,
                  XSECXPathNodeList& toRemove,
                  XSECSafeBufferFormatter* formatter,
                  XSECNameSpaceExpander* nse);

bool separator(unsigned char c);
XalanNode* findHereNodeFromXalan(XercesWrapperNavigator* xwn, XalanNode* n, DOMNode* h);


TXFMXPathFilter::TXFMXPathFilter(DOMDocument* doc) :
    TXFMBase(doc) {

    document = NULL;
    XSECnew(mp_formatter, XSECSafeBufferFormatter("UTF-8",XMLFormatter::NoEscapes,
                                                XMLFormatter::UnRep_CharRef));
}

TXFMXPathFilter::~TXFMXPathFilter() {

    lstsVectorType::iterator i;

    for (i = m_lsts.begin(); i != m_lsts.end(); ++i) {

        if ((*i)->lst != NULL)
            delete ((*i)->lst);

        delete (*i);
    }

    if (mp_formatter != NULL)
        delete mp_formatter;
}

// Methods to set the inputs

void TXFMXPathFilter::setInput(TXFMBase* newInput) {

    if (newInput->getOutputType() == TXFMBase::BYTE_STREAM) {

        // Need to parse into DOM_NODES

        TXFMParser * parser;
        XSECnew(parser, TXFMParser(mp_expansionDoc));
        try{
            parser->setInput(newInput);
        }
        catch (...) {
            delete parser;
            input = newInput;
            throw;
        }

        input = parser;
        parser->expandNameSpaces();
    }
    else {
        input = newInput;
    }

    // Set up for the new document
    document = input->getDocument();

    // Expand if necessary
    this->expandNameSpaces();

    keepComments = input->getCommentsStatus();
}

XSECXPathNodeList* TXFMXPathFilter::evaluateSingleExpr(DSIGXPathFilterExpr* expr) {

    // Have a single expression that we wish to find the resultant nodeset for

    XSECXPathNodeList addedNodes;
    setXPathNS(document, expr->mp_NSMap, addedNodes, mp_formatter, mp_nse);

    XPathProcessorImpl    xppi;                    // The processor
    XercesParserLiaison xpl;
    XercesDOMSupport    xds(xpl);
    XPathEvaluator        xpe;
    XPathFactoryDefault xpf;
    XPathConstructionContextDefault xpcc;

    XalanDocument        * xd;
    XalanNode            * contextNode;

    // Xalan can throw exceptions in all functions, so do one broad catch point.

    try {

        // Map to Xalan
        xd = xpl.createDocument(document);

        // For performing mapping
        XercesDocumentWrapper *xdw = xpl.mapDocumentToWrapper(xd);
        XercesWrapperNavigator xwn(xdw);

        // Map the "here" node

        XalanNode * hereNode = NULL;

        hereNode = xwn.mapNode(expr->mp_xpathFilterNode);

        if (hereNode == NULL) {

            hereNode = findHereNodeFromXalan(&xwn, xd, expr->mp_exprTextNode);

            if (hereNode == NULL) {
                throw XSECException(XSECException::XPathFilterError,
                   "Unable to find here node in Xalan Wrapper map");
            }
        }

        // Now work out what we have to set up in the new processing

        XalanDOMString cd;        // For XPath Filter, the root is always the context node

        cd = XalanDOMString("/");        // Root node

        // The context node is the "root" node
        contextNode =
            xpe.selectSingleNode(
            xds,
            xd,
            cd.c_str(),
            xd->getDocumentElement());

        XPathEnvSupportDefault xpesd;
        XObjectFactoryDefault            xof;
        XPathExecutionContextDefault    xpec(xpesd, xds, xof);

        ElementPrefixResolverProxy pr(xd->getDocumentElement(), xpesd, xds);

        // Work around the fact that the XPath implementation is designed for XSLT, so does
        // not allow here() as a NCName.

        // THIS IS A KLUDGE AND SHOULD BE DONE BETTER

        safeBuffer k(KLUDGE_PREFIX);
        k.sbStrcatIn(":");

        // Map the expression into a local code page string (silly - should be XMLCh)
        safeBuffer exprSB;
        exprSB << (*mp_formatter << expr->m_expr.rawXMLChBuffer());

        XMLSSize_t offset = exprSB.sbStrstr("here()");

        while (offset >= 0) {

            if (offset == 0 || offset == 1 ||
                (!(exprSB[offset - 1] == ':' && exprSB[offset - 2] != ':') &&
                separator(exprSB[offset - 1]))) {

                exprSB.sbStrinsIn(k.rawCharBuffer(), offset);
            }

            offset = exprSB.sbOffsetStrstr("here()", offset + 11);
        }

        // Install the External function in the Environment handler

        if (hereNode != NULL) {
            xpesd.installExternalFunctionLocal(XalanDOMString(URI_ID_DSIG), XalanDOMString("here"), DSIGXPathHere(hereNode));
        }

        XPath * xp = xpf.create();

        XalanDOMString Xexpr((char *) exprSB.rawBuffer());
        xppi.initXPath(*xp, xpcc, Xexpr, pr);

        // Now resolve

        XObjectPtr xObj = xp->execute(contextNode, pr, xpec);

        // Now map to a list that others can use (naieve list at this time)

        const NodeRefListBase&    lst = xObj->nodeset();

        int size = (int) lst.getLength();
        const DOMNode *item;

        XSECXPathNodeList * ret;
        XSECnew(ret, XSECXPathNodeList);
        Janitor<XSECXPathNodeList> j_ret(ret);

        for (int i = 0; i < size; ++ i) {

            if (lst.item(i) == xd)
                ret->addNode(document);
            else {
                item = xwn.mapNode(lst.item(i));
                ret->addNode(item);
            }
        }

        xpesd.uninstallExternalFunctionGlobal(XalanDOMString(URI_ID_DSIG), XalanDOMString("here"));

        clearXPathNS(document, addedNodes, mp_formatter, mp_nse);

        j_ret.release();
        return ret;
    }
    catch (const XSLException &e) {

        safeBuffer msg;

        // Whatever happens - fix any changes to the original document
        clearXPathNS(document, addedNodes, mp_formatter, mp_nse);

        // Collate the exception message into an XSEC message.
        msg.sbTranscodeIn("Xalan Exception : ");
        msg.sbXMLChCat(e.getType());
        msg.sbXMLChCat(" caught.  Message : ");
        msg.sbXMLChCat(e.getMessage().c_str());

        throw XSECException(XSECException::XPathFilterError,
            msg.rawXMLChBuffer());
    }
    catch (...) {
        clearXPathNS(document, addedNodes, mp_formatter, mp_nse);
        throw;
    }

    return NULL;
}

bool TXFMXPathFilter::checkNodeInInput(DOMNode* n, DOMNode* attParent) {

    if (mp_fragment != NULL) {

        DOMNode * p = n;

        /* Check attParent here to reduce cycles */
        if (attParent != NULL) {
            if (p == mp_fragment)
                return true;
            p = attParent;
        }

        while (p != NULL) {

            if (p == mp_fragment)
                return true;

            p = p->getParentNode();
        }

        return false;
    }

    return mp_inputList->hasNode(n);
}

bool TXFMXPathFilter::checkNodeInScope(DOMNode* n) {

    // Walk backwards through the lists
    lstsVectorType::iterator lstsIter;

    lstsIter = m_lsts.end();
    filterSetHolder *sh;

    while (lstsIter != m_lsts.begin()) {

        lstsIter--;
        sh = *lstsIter;
        if (sh->ancestorInScope != NULL) {

            // Have an ancestor in scope, so this node is in this list
            if (sh->type == DSIGXPathFilterExpr::FILTER_UNION)
                // Got this far, must be OK!
                return true;
            if (sh->type == DSIGXPathFilterExpr::FILTER_SUBTRACT)
                return false;
        }
        else {
            if (sh->type == DSIGXPathFilterExpr::FILTER_INTERSECT)
                return false;
        }
    }

    return true;
}

#if 1

void TXFMXPathFilter::walkDocument(DOMNode* n) {

    // Non-recursive version

    DOMNode * current = n;
    DOMNode * next;
    DOMNode * attParent = NULL;     /* Assign NULL to remove spurious Forte warning */
    bool done = false;
    bool treeUp = false;
    DOMNamedNodeMap * atts = n->getAttributes();
    int attsSize = -1;
    int currentAtt = -1;
    lstsVectorType::iterator lstsIter;

    while (done == false && current != NULL) {

        if (treeUp == true) {

            if (current == n) {
                // We are complete.
                done = true;
            }
            else {
                // Remove this node from the ancestor lists
                for (lstsIter = m_lsts.begin(); lstsIter != m_lsts.end(); ++lstsIter) {

                    if ((*lstsIter)->ancestorInScope == current) {
                        (*lstsIter)->ancestorInScope = NULL;
                    }
                }

                // Check for another sibling
                next = current->getNextSibling();

                if (next == NULL) {
                    current = current->getParentNode();
                    treeUp = true;
                }
                else {
                    current = next;
                    treeUp = false;
                }
            }
        } /* treeUp == true */
        else {
            // Check if the current node is in the result set.  The walk the children

            // First check if this node is in any lists, and if so,
            // set the appropriate ancestor nodes (if necessary)

            for (lstsIter = m_lsts.begin(); lstsIter != m_lsts.end(); ++lstsIter) {

                if ((*lstsIter)->ancestorInScope == NULL &&
                    (*lstsIter)->lst->hasNode(current)) {

                    (*lstsIter)->ancestorInScope = current;
                }
            }

            // Now that the ancestor setup is done, check to see if this node is
            // in scope.

            if (checkNodeInScope(current) &&
                checkNodeInInput(current, (atts != NULL ? attParent : NULL))) {

                m_xpathFilterMap.addNode(current);
            }

            // Now find the next node!

            if (atts != NULL) {

                // Working on an attribute list
                currentAtt++;

                if (currentAtt == attsSize) {

                    // Attribute list complete
                    atts = NULL;
                    current = attParent;
                    next = current->getFirstChild();
                    if (next == NULL)
                        treeUp = true;
                    else {
                        current = next;
                        treeUp = false;
                    }
                }
                else {
                    current = atts->item(currentAtt);
                }
            }
            else {
                // Working on an element or other non-attribute node
                atts = current->getAttributes();

                if (atts != NULL && ((attsSize = atts->getLength()) > 0)) {
                    currentAtt = 0;
                    attParent = current;
                    current = atts->item(0);
                    treeUp = false;
                }
                else {
                    atts = NULL;

                    next = current->getFirstChild();

                    if (next != NULL) {
                        current = next;
                        treeUp = false;
                    }
                    else {
                        treeUp = true;
                    }
                }
            } /* ! atts == NULL */
        }
    } /* while */
}
#endif

#if 0

void TXFMXPathFilter::walkDocument(DOMNode * n) {

    // Check if the current node is in the result set.  The walk the children
    lstsVectorType::iterator lstsIter;

    // First check if this node is in any lists, and if so,
    // set the appropriate ancestor nodes (if necessary)

    for (lstsIter = m_lsts.begin(); lstsIter != m_lsts.end(); ++lstsIter) {

        if ((*lstsIter)->ancestorInScope == NULL && (*lstsIter)->lst->hasNode(n)) {
            (*lstsIter)->ancestorInScope = n;
        }
    }

    // Now that the ancestor setup is done, check to see if this node is
    // in scope.

    if (checkNodeInScope(n) && checkNodeInInput(n)) {
        m_xpathFilterMap.addNode(n);
    }

    // Do any attributes

    DOMNamedNodeMap * atts = n->getAttributes();
    if (atts != NULL) {
        unsigned int s = atts->getLength();
        for (unsigned int i = 0; i < s; ++i) {
            walkDocument(atts->item(i));
        }
    }

    // Do any childeren

    DOMNode * c = n->getFirstChild();

    while (c != NULL) {
        walkDocument(c);
        c = c->getNextSibling();
    }

    // Now remove from ancestor lists if we are that ancestor
    for (lstsIter = m_lsts.begin(); lstsIter != m_lsts.end(); ++lstsIter) {

        if ((*lstsIter)->ancestorInScope == n) {
            (*lstsIter)->ancestorInScope = NULL;
        }
    }
}
#endif

void TXFMXPathFilter::evaluateExprs(DSIGTransformXPathFilter::exprVectorType* exprs) {

    if (exprs == NULL || exprs->size() < 1) {
        throw XSECException(XSECException::XPathFilterError,
            "TXFMXPathFilter::evaluateExpr - no expression list set");
    }

    DSIGTransformXPathFilter::exprVectorType::iterator i;

    for (i = exprs->begin(); i != exprs->end(); ++i) {

        XSECXPathNodeList * lst = evaluateSingleExpr(*i);
        filterSetHolder * sh;
        XSECnew(sh, filterSetHolder);

        sh->lst = lst;
        sh->type = (*i)->m_filterType;
        sh->ancestorInScope = NULL;

        if (lst != NULL) {
            m_lsts.push_back(sh);
        }
    }

    // Well we appear to have successfully run through all the nodelists!

    mp_fragment = NULL;
    mp_inputList = NULL;

    // Find the input nodeset
    TXFMBase::nodeType inputType = input->getNodeType();
    switch (inputType) {

    case DOM_NODE_DOCUMENT :
        mp_fragment = document;
        break;

    case DOM_NODE_DOCUMENT_FRAGMENT :
        mp_fragment = input->getFragmentNode();
        break;

    case DOM_NODE_XPATH_NODESET :
        mp_inputList = &(input->getXPathNodeList());
        break;

    default :
        throw XSECException(XSECException::XPathFilterError,
            "TXFMXPathFilter::evaluateExprs - unknown input type");

    }

    // Now just recurse through each node in the document
    walkDocument(document);
}


// Methods to get tranform output type and input requirement

TXFMBase::ioType TXFMXPathFilter::getInputType() const {
    return TXFMBase::DOM_NODES;
}

TXFMBase::ioType TXFMXPathFilter::getOutputType() const {
    return TXFMBase::DOM_NODES;
}

TXFMBase::nodeType TXFMXPathFilter::getNodeType() const {
    return TXFMBase::DOM_NODE_XPATH_NODESET;
}

// Methods to get output data

unsigned int TXFMXPathFilter::readBytes(XMLByte* const toFill, unsigned int maxToFill) {
    return 0;
}

DOMDocument* TXFMXPathFilter::getDocument() const {
    return document;
}

XSECXPathNodeList& TXFMXPathFilter::getXPathNodeList() {
    return m_xpathFilterMap;
}

#endif /* XSEC_HAVE_XPATH */



