blob: cba9361c4518d615dee2eec11eb354803096b147 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xalan" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, International
* Business Machines, Inc., http://www.ibm.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
// Class header file.
#include "VariablesStack.hpp"
#include <algorithm>
#include "ElemVariable.hpp"
#include "StylesheetExecutionContext.hpp"
VariablesStack::VariablesStack() :
m_stack(),
m_globalStackFrameIndex(-1),
m_globalStackFrameMarked(false),
m_currentStackFrameIndex(0),
m_guardStack()
{
m_stack.reserve(eDefaultStackSize);
}
VariablesStack::~VariablesStack()
{
}
void
VariablesStack::reset()
{
while(m_stack.size() > 0)
{
pop();
}
m_stack.clear();
m_guardStack.clear();
m_globalStackFrameMarked = false;
m_globalStackFrameIndex = -1;
}
bool
VariablesStack::elementFrameAlreadyPushed(const ElemTemplateElement* elem) const
{
const VariableStackStackType::size_type nElems = m_stack.size();
assert(nElems > 0);
// There is guaranteed to be a context marker at
// the bottom of the stack, so i should stop at
// 1.
for(VariableStackStackType::size_type i = nElems - 1; i > 0; --i)
{
const StackEntry& theEntry = m_stack[i];
if(theEntry.getType() == StackEntry::eElementFrameMarker)
{
if(theEntry.getElement() == elem)
{
return true;
}
}
}
return false;
}
void
VariablesStack::pushContextMarker()
{
push(StackEntry());
}
void
VariablesStack::popContextMarker()
{
const VariableStackStackType::size_type nElems = m_stack.size();
for(VariableStackStackType::size_type i = nElems; i > 0 && m_stack.empty() == false; --i)
{
const StackEntry& theEntry = m_stack[i - 1];
assert(theEntry == back());
const StackEntry::eType type = theEntry.getType();
assert(type < StackEntry::eNextValue && type >= 0);
pop();
if (type == StackEntry::eContextMarker)
{
break;
}
}
}
class CommitPushElementFrame
{
public:
CommitPushElementFrame(
VariablesStack& theVariableStack,
const ElemTemplateElement* targetTemplate) :
m_variableStack(&theVariableStack),
m_targetTemplate(targetTemplate)
{
theVariableStack.pushElementFrame(targetTemplate);
}
~CommitPushElementFrame()
{
if (m_variableStack != 0)
{
m_variableStack->popElementFrame(m_targetTemplate);
}
}
void
commit()
{
m_variableStack = 0;
}
private:
VariablesStack* m_variableStack;
const ElemTemplateElement* const m_targetTemplate;
};
void
VariablesStack::push(const StackEntry& theEntry)
{
assert(theEntry.getType() < StackEntry::eNextValue && theEntry.getType() >= 0);
if(m_currentStackFrameIndex == m_stack.size())
{
++m_currentStackFrameIndex;
}
m_stack.push_back(theEntry);
// Increment the global stack frame index as long as we're pushing variables, and
// it already hasn't been marked. This is a temporary work-around for problems
// with evaluating top-level variables as they're pushed, rather than as they're
// used.
if (m_globalStackFrameMarked == false && theEntry.getType() == StackEntry::eVariable)
{
m_globalStackFrameIndex = m_currentStackFrameIndex;
}
}
void
VariablesStack::pop()
{
assert(m_stack.empty() == false);
if(m_currentStackFrameIndex == m_stack.size())
{
--m_currentStackFrameIndex;
}
m_stack.pop_back();
}
const void
VariablesStack::PushParamFunctor::operator()(const VariablesStack::ParamsVectorType::value_type& theEntry)
{
assert(theEntry.m_qname != 0);
if (theEntry.m_value.null() == false)
{
m_variablesStack.push(VariablesStack::StackEntry(theEntry.m_qname, theEntry.m_value, true));
}
else
{
assert(theEntry.m_variable != 0);
m_variablesStack.push(VariablesStack::StackEntry(theEntry.m_qname, theEntry.m_variable, true));
}
}
void
VariablesStack::pushParams(
const ParamsVectorType& theParams,
const ElemTemplateElement* targetTemplate)
{
// This object will push an element marker, and pop it
// if we don't call it's commit() member function. So
// if an exception is thrown while transferring the
// parameters, the element marker will be popped.
// This keeps the stack in a consistent state.
// It will also delete things left in the temp stack
// as well.
CommitPushElementFrame thePusher(*this,
targetTemplate);
#if !defined (XALAN_NO_NAMESPACES)
using std::for_each;
#endif
for_each(theParams.begin(), theParams.end(), PushParamFunctor(*this));
thePusher.commit();
}
void
VariablesStack::pushVariable(
const XalanQName& name,
const ElemVariable* var,
const ElemTemplateElement* e)
{
if(elementFrameAlreadyPushed(e) == false)
{
pushElementFrame(e);
}
push(StackEntry(&name, var));
}
void
VariablesStack::pushVariable(
const XalanQName& name,
const XObjectPtr& val,
const ElemTemplateElement* e)
{
if(elementFrameAlreadyPushed(e) == false)
{
throw InvalidStackContextException();
}
push(StackEntry(&name, val));
}
void
VariablesStack::start()
{
}
void
VariablesStack::resetParams()
{
const unsigned int nElems = getCurrentStackFrameIndex();
// There is guaranteed to be a context marker at
// the bottom of the stack, so i should stop at
// 1.
for(unsigned int i = nElems - 1; i > 0; --i)
{
StackEntry& theEntry = m_stack[i];
if(theEntry.getType() == StackEntry::eContextMarker)
{
break;
}
else
{
theEntry.deactivate();
}
}
}
void
VariablesStack::markGlobalStackFrame()
{
m_globalStackFrameIndex = m_stack.size();
m_globalStackFrameMarked = true;
pushContextMarker();
}
void
VariablesStack::unmarkGlobalStackFrame()
{
popContextMarker();
m_globalStackFrameIndex = -1;
m_globalStackFrameMarked = false;
}
const XObjectPtr
VariablesStack::findXObject(
const XalanQName& name,
StylesheetExecutionContext& executionContext,
bool fIsParam,
bool fSearchGlobalSpace,
bool& fNameFound)
{
typedef VariableStackStackType::size_type size_type;
// findEntry() returns an index into the stack. We should
// _never_ take the address of anything in the stack, since
// the address could change at unexpected times.
size_type theEntryIndex =
findEntry(name, fIsParam, fSearchGlobalSpace);
if (theEntryIndex == m_stack.size())
{
fNameFound = false;
return XObjectPtr();
}
else
{
assert(theEntryIndex < m_stack.size());
fNameFound = true;
assert(m_stack[theEntryIndex].getType() == StackEntry::eVariable ||
m_stack[theEntryIndex].getType() == StackEntry::eParam ||
m_stack[theEntryIndex].getType() == StackEntry::eActiveParam);
const XObjectPtr& theValue = m_stack[theEntryIndex].getValue();
if (theValue.null() == false)
{
return theValue;
}
else
{
const ElemVariable* const var = m_stack[theEntryIndex].getVariable();
XObjectPtr theNewValue;
if (var != 0)
{
XalanNode* const doc = executionContext.getRootDocument();
assert(doc != 0);
#if !defined (XALAN_NO_NAMESPACES)
using std::find;
#endif
// See if the ElemVariable instance is already being evaluated...
if (find(m_guardStack.begin(), m_guardStack.end(), var) != m_guardStack.end())
{
executionContext.error(
"A circular variable definition was detected",
doc,
var->getLocator());
}
m_guardStack.push_back(var);
// We need to set up a stack frame for the variable's execution...
typedef StylesheetExecutionContext::PushAndPopContextMarker PushAndPopContextMarker;
const PushAndPopContextMarker theContextMarkerPushPop(executionContext);
theNewValue = var->getValue(executionContext, doc);
assert(theNewValue.null() == false);
assert(m_guardStack.empty() == false);
m_guardStack.pop_back();
m_stack[theEntryIndex].setValue(theNewValue);
m_stack[theEntryIndex].activate();
}
return theNewValue;
}
}
}
VariablesStack::VariableStackStackType::size_type
VariablesStack::findEntry(
const XalanQName& qname,
bool fIsParam,
bool fSearchGlobalSpace)
{
typedef VariableStackStackType::size_type size_type;
size_type theEntryIndex = m_stack.size();
const unsigned int nElems = getCurrentStackFrameIndex();
// There is guaranteed to be a context marker at
// the bottom of the stack, so i should stop at
// 1.
for(unsigned int i = nElems - 1; i > 0; --i)
{
StackEntry& theEntry = m_stack[i];
const StackEntry::eType theType = theEntry.getType();
if(theType == StackEntry::eVariable ||
theType == StackEntry::eActiveParam)
{
assert(theEntry.getName() != 0);
if(theEntry.getName()->equals(qname))
{
theEntryIndex = size_type(i);
break;
}
}
else if (theType == StackEntry::eParam)
{
if (fIsParam == true)
{
if(theEntry.getName()->equals(qname))
{
theEntry.activate();
theEntryIndex = size_type(i);
break;
}
}
}
else if(theType == StackEntry::eContextMarker)
{
break;
}
}
if(theEntryIndex == m_stack.size() && fIsParam == false && true == fSearchGlobalSpace && m_globalStackFrameIndex > 1)
{
// Look in the global space
for(unsigned int i = m_globalStackFrameIndex - 1; i > 0; i--)
{
StackEntry& theEntry = m_stack[i];
const StackEntry::eType theType = theEntry.getType();
if(theType == StackEntry::eVariable)
{
assert(theEntry.getName() != 0);
if(theEntry.getName()->equals(qname))
{
theEntryIndex = size_type(i);
break;
}
}
else if(theType == StackEntry::eContextMarker)
{
break;
}
}
}
return theEntryIndex;
}
void
VariablesStack::pushElementFrame(const ElemTemplateElement* elem)
{
push(StackEntry(elem));
}
class EnsurePop
{
public:
EnsurePop(VariablesStack& theVariablesStack) :
m_variablesStack(theVariablesStack)
{
}
~EnsurePop()
{
m_variablesStack.pop();
}
private:
VariablesStack& m_variablesStack;
};
void
VariablesStack::popElementFrame(const ElemTemplateElement* elem)
{
const VariableStackStackType::size_type nElems = m_stack.size();
assert(nElems > 0);
// There is guaranteed to be a context marker at
// the bottom of the stack, so i should stop at
// 1.
for(VariableStackStackType::size_type i = nElems - 1; i > 0; --i)
{
const StackEntry& theEntry = m_stack[i];
// Guarantee that it will be popped when we're done.
EnsurePop theEnsurePop(*this);
if(theEntry.getType() == StackEntry::eContextMarker)
{
throw InvalidStackContextException();
}
else if (theEntry.getType() == StackEntry::eElementFrameMarker)
{
const ElemTemplateElement* const theElement =
theEntry.getElement();
if (theElement != elem)
{
throw InvalidStackContextException();
}
break;
}
}
}
VariablesStack::StackEntry::StackEntry() :
m_type(eContextMarker),
m_qname(0),
m_value(),
m_variable(0),
m_element(0)
{
}
VariablesStack::StackEntry::StackEntry(
const XalanQName* name,
const XObjectPtr& val,
bool isParam) :
m_type(isParam == true ? eParam : eVariable),
m_qname(name),
m_value(val),
m_variable(0),
m_element(0)
{
}
VariablesStack::StackEntry::StackEntry(
const XalanQName* name,
const ElemVariable* var,
bool isParam) :
m_type(isParam == true ? eParam : eVariable),
m_qname(name),
m_value(),
m_variable(var),
m_element(0)
{
}
VariablesStack::StackEntry::StackEntry(const ElemTemplateElement* elem) :
m_type(eElementFrameMarker),
m_qname(0),
m_value(),
m_variable(0),
m_element(elem)
{
}
VariablesStack::StackEntry::StackEntry(const StackEntry& theSource) :
m_type(theSource.m_type),
m_qname(theSource.m_qname),
m_value(theSource.m_value),
m_variable(theSource.m_variable),
m_element(theSource.m_element)
{
}
VariablesStack::StackEntry::~StackEntry()
{
}
VariablesStack::StackEntry&
VariablesStack::StackEntry::operator=(const StackEntry& theRHS)
{
if (this != &theRHS)
{
m_type = theRHS.m_type;
m_qname = theRHS.m_qname;
m_value = theRHS.m_value;
m_variable = theRHS.m_variable;
m_element = theRHS.m_element;
}
return *this;
}
// Equality for StackEntry instances is probably bogus,
// so it might be worthwhile to just get rid of this.
bool
VariablesStack::StackEntry::operator==(const StackEntry& theRHS) const
{
bool fResult = false;
if (m_type == theRHS.m_type)
{
if (m_type == eContextMarker)
{
if (&theRHS == this)
{
fResult = true;
}
}
else if (m_type == eVariable || m_type == eParam || m_type == eActiveParam)
{
// We only need to compare the variable related members...
if (m_value == theRHS.m_value ||
m_variable == theRHS.m_variable)
{
fResult = true;
}
}
else if (m_type == eElementFrameMarker)
{
if (m_element == theRHS.m_element)
{
fResult = true;
}
}
else
{
assert(0);
}
}
return fResult;
}
void
VariablesStack::StackEntry::activate()
{
if (m_type == eParam)
{
m_type = eActiveParam;
}
}
void
VariablesStack::StackEntry::deactivate()
{
if (m_type == eActiveParam)
{
m_type = eParam;
}
}
VariablesStack::InvalidStackContextException::InvalidStackContextException() :
XSLTProcessorException(TranscodeFromLocalCodePage("Invalid stack context"),
TranscodeFromLocalCodePage("InvalidStackContextException"))
{
}
VariablesStack::InvalidStackContextException::~InvalidStackContextException()
{
}