blob: 222658ec4107a4e227941cd60e7c914d89619c1b [file] [log] [blame]
/*
* 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.
*/
// Class header file.
#include "VariablesStack.hpp"
#include <algorithm>
#include <xalanc/PlatformSupport/XalanMessageLoader.hpp>
#include "ElemVariable.hpp"
#include "StylesheetExecutionContext.hpp"
namespace XALAN_CPP_NAMESPACE {
VariablesStack::VariablesStack(MemoryManager& theManager) :
m_stack(theManager),
m_globalStackFrameIndex(~0u),
m_globalStackFrameMarked(false),
m_currentStackFrameIndex(0),
m_guardStack(theManager),
m_elementFrameStack(theManager)
{
m_stack.reserve(eDefaultStackSize);
}
VariablesStack::~VariablesStack()
{
}
void
VariablesStack::reset()
{
while(m_stack.empty() == false)
{
pop();
}
m_stack.clear();
m_guardStack.clear();
m_elementFrameStack.clear();
m_globalStackFrameMarked = false;
m_globalStackFrameIndex = ~0u;
}
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()
{
VariableStackStackType::iterator i = m_stack.end();
for(; ;)
{
assert(i != m_stack.begin());
const StackEntry& theEntry = *--i;
assert(theEntry == back());
const StackEntry::eType type = theEntry.getType();
assert(type < StackEntry::eNextValue && type >= 0);
pop();
if (type == StackEntry::eContextMarker)
{
break;
}
}
// This introduced a regression into 1.7, because of all the
// external fiddling with the stack frame index. We need to
// re-write that code so no external meddling is necessary,
// so we can track the stack frame index properly. In particular,
// this is an issue with xsl:apply-templates, because a context
// marker is pushed before its select attribute is evaluated, even
// though the select expression really should be evaluated in
// the previous stack context.
// m_currentStackFrameIndex = size_type(m_stack.size());
}
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();
}
void
VariablesStack::PushParamFunctor::operator()(const VariablesStack::ParamsVectorType::value_type& theEntry) const
{
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));
}
}
VariablesStack::CommitPushParams::CommitPushParams(VariablesStack& theVariablesStack) :
m_variablesStack(&theVariablesStack),
m_stackSize(theVariablesStack.getStackSize())
{
}
VariablesStack::CommitPushParams::~CommitPushParams()
{
if (m_variablesStack != 0)
{
while(m_variablesStack->getStackSize() > m_stackSize)
{
m_variablesStack->pop();
}
}
}
void
VariablesStack::pushParams(const ParamsVectorType& theParams)
{
// This object will push the params and pop them
// if we don't call it's commit() member function. So
// if an exception is thrown while transferring the
// parameters, the stack stays in a consistent state.
CommitPushParams thePusher(*this);
using std::for_each;
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)
{
XalanDOMString theBuffer(m_stack.getMemoryManager());
throw InvalidStackContextException(theBuffer);
}
push(StackEntry(&name, val));
}
void
VariablesStack::start()
{
}
void
VariablesStack::resetParams()
{
const size_type nElems = getCurrentStackFrameIndex();
assert(nElems > 0);
// There is guaranteed to be a context marker at
// the bottom of the stack, so i should stop at
// 1.
for(size_type 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 = size_type(m_stack.size());
m_globalStackFrameMarked = true;
pushContextMarker();
}
void
VariablesStack::unmarkGlobalStackFrame()
{
popContextMarker();
m_globalStackFrameIndex = ~0u;
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.
const 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);
using std::find;
// See if the ElemVariable instance is already being evaluated...
if (find(m_guardStack.begin(), m_guardStack.end(), var) != m_guardStack.end())
{
const StylesheetExecutionContext::GetCachedString theGuard(executionContext);
executionContext.problem(
StylesheetExecutionContext::eXSLTProcessor,
StylesheetExecutionContext::eError,
XalanMessageLoader::getMessage(
theGuard.get(),
XalanMessages::CircularVariableDefWasDetected),
var->getLocator(),
doc);
}
m_guardStack.push_back(var);
#if !defined(XALAN_RECURSIVE_STYLESHEET_EXECUTION)
executionContext.pushContextMarker();
#else
// We need to set up a stack frame for the variable's execution...
typedef StylesheetExecutionContext::PushAndPopContextMarker PushAndPopContextMarker;
const PushAndPopContextMarker theContextMarkerPushPop(executionContext);
#endif
theNewValue = var->getValue(executionContext, doc);
assert(theNewValue.null() == false);
#if !defined(XALAN_RECURSIVE_STYLESHEET_EXECUTION)
executionContext.popContextMarker();
#endif
assert(m_guardStack.empty() == false);
m_guardStack.pop_back();
m_stack[theEntryIndex].setValue(theNewValue);
m_stack[theEntryIndex].activate();
}
return theNewValue;
}
}
}
VariablesStack::size_type
VariablesStack::findEntry(
const XalanQName& qname,
bool fIsParam,
bool fSearchGlobalSpace)
{
size_type theEntryIndex = size_type(m_stack.size());
const size_type nElems = getCurrentStackFrameIndex();
// There is guaranteed to be a context marker at
// the bottom of the stack, so i should stop at
// 1.
for(size_type 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 = i;
break;
}
}
else if (theType == StackEntry::eParam)
{
if (fIsParam == true)
{
if(theEntry.getName()->equals(qname))
{
theEntry.activate();
theEntryIndex = 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(size_type 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 = i;
break;
}
}
else if(theType == StackEntry::eContextMarker)
{
break;
}
}
}
return theEntryIndex;
}
void
VariablesStack::pushElementFrame(const ElemTemplateElement* elem)
{
push(StackEntry(elem));
#if !defined(NDEBUG)
m_elementFrameStack.push_back(elem);
#endif
}
class EnsurePop
{
public:
EnsurePop(VariablesStack& theVariablesStack) :
m_variablesStack(theVariablesStack)
{
}
~EnsurePop()
{
m_variablesStack.pop();
}
private:
VariablesStack& m_variablesStack;
};
void
VariablesStack::popElementFrame()
{
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.
const EnsurePop theEnsurePop(*this);
if(theEntry.getType() == StackEntry::eContextMarker)
{
XalanDOMString theBuffer(m_stack.getMemoryManager());
throw InvalidStackContextException(theBuffer);
}
else if (theEntry.getType() == StackEntry::eElementFrameMarker)
{
#if !defined(NDEBUG)
const ElemTemplateElement* const theElement =
theEntry.getElement();
if (m_elementFrameStack.empty() == true)
{
XalanDOMString theBuffer(m_stack.getMemoryManager());
throw InvalidStackContextException(theBuffer);
}
const ElemTemplateElement* const theStackBack =
m_elementFrameStack.back();
m_elementFrameStack.pop_back();
if (theElement != theStackBack)
{
XalanDOMString theBuffer(m_stack.getMemoryManager());
throw InvalidStackContextException(theBuffer);
}
#endif
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;
}
}
const XalanDOMChar VariablesStack::InvalidStackContextException::m_type[] =
{
XalanUnicode::charLetter_I,
XalanUnicode::charLetter_n,
XalanUnicode::charLetter_v,
XalanUnicode::charLetter_a,
XalanUnicode::charLetter_l,
XalanUnicode::charLetter_i,
XalanUnicode::charLetter_d,
XalanUnicode::charLetter_S,
XalanUnicode::charLetter_t,
XalanUnicode::charLetter_a,
XalanUnicode::charLetter_c,
XalanUnicode::charLetter_k,
XalanUnicode::charLetter_C,
XalanUnicode::charLetter_o,
XalanUnicode::charLetter_n,
XalanUnicode::charLetter_t,
XalanUnicode::charLetter_e,
XalanUnicode::charLetter_x,
XalanUnicode::charLetter_t,
XalanUnicode::charLetter_E,
XalanUnicode::charLetter_x,
XalanUnicode::charLetter_c,
XalanUnicode::charLetter_e,
XalanUnicode::charLetter_p,
XalanUnicode::charLetter_t,
XalanUnicode::charLetter_i,
XalanUnicode::charLetter_o,
XalanUnicode::charLetter_n,
0
};
VariablesStack::InvalidStackContextException::InvalidStackContextException(XalanDOMString& theResult) :
XSLTProcessorException(
theResult.getMemoryManager(),
XalanMessageLoader::getMessage(
theResult,
XalanMessages::InvalidStackContext))
{
}
VariablesStack::InvalidStackContextException::~InvalidStackContextException()
{
}
}