blob: b5483a5693b6de80bffb2f8efd51d2f8b5bf022c [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 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/>.
*/
#include "ElemNumber.hpp"
#include <sax/AttributeList.hpp>
#include <PlatformSupport/DOMStringHelper.hpp>
#include <PlatformSupport/NumberFormat.hpp>
#include <XPath/XPath.hpp>
#include "Constants.hpp"
#include "NumeratorFormatter.hpp"
#include "StylesheetConstructionContext.hpp"
#include "StylesheetExecutionContext.hpp"
#if !defined(XALAN_NO_NAMESPACES)
using std::vector;
#endif
typedef vector<DOMString> StringVectorType;
typedef StringVectorType::iterator StringVectorTypeIterator;
const DOMString ElemNumber::s_alphaCountTable(XALAN_STATIC_UCODE_STRING("ZABCDEFGHIJKLMNOPQRSTUVWXY"));
const DecimalToRoman ElemNumber::s_romanConvertTable[] =
{
DecimalToRoman(1000, XALAN_STATIC_UCODE_STRING("M"), 900, XALAN_STATIC_UCODE_STRING("CM")),
DecimalToRoman(500, XALAN_STATIC_UCODE_STRING("D"), 400, XALAN_STATIC_UCODE_STRING("CD")),
DecimalToRoman(100L, XALAN_STATIC_UCODE_STRING("C"), 90L, XALAN_STATIC_UCODE_STRING("XC")),
DecimalToRoman(50L, XALAN_STATIC_UCODE_STRING("L"), 40L, XALAN_STATIC_UCODE_STRING("XL")),
DecimalToRoman(10L, XALAN_STATIC_UCODE_STRING("X"), 9L, XALAN_STATIC_UCODE_STRING("IX")),
DecimalToRoman(5L, XALAN_STATIC_UCODE_STRING("V"), 4L, XALAN_STATIC_UCODE_STRING("IV")),
DecimalToRoman(1L, XALAN_STATIC_UCODE_STRING("I"), 1L, XALAN_STATIC_UCODE_STRING("I"))
};
ElemNumber::ElemNumber(
StylesheetConstructionContext& constructionContext,
Stylesheet& stylesheetTree,
const DOMString& name,
const AttributeList& atts,
int lineNumber,
int columnNumber) :
ElemTemplateElement(constructionContext,
stylesheetTree,
name,
lineNumber,
columnNumber),
m_countMatchPattern(0),
m_fromMatchPattern(0),
m_valueExpr(0),
m_level(Constants::NUMBERLEVEL_SINGLE),
m_format_avt(),
m_lang_avt(),
m_lettervalue_avt(),
m_groupingSeparator_avt(),
m_groupingSize_avt()
{
const int nAttrs = atts.getLength();
for(int i = 0; i < nAttrs; i++)
{
const DOMString aname(atts.getName(i));
if(equals(aname, Constants::ATTRNAME_LEVEL))
{
const DOMString levelValue = atts.getValue(i);
if(!isEmpty(levelValue))
{
if(equals(Constants::ATTRVAL_MULTI, levelValue))
m_level = Constants::NUMBERLEVEL_MULTI;
else if(equals(levelValue,Constants::ATTRVAL_ANY))
m_level = Constants::NUMBERLEVEL_ANY;
else if(equals(levelValue,Constants::ATTRVAL_SINGLE))
m_level = Constants::NUMBERLEVEL_SINGLE;
else
error("Bad value on level attribute: " + levelValue);
}
}
else if(equals(aname, Constants::ATTRNAME_COUNT))
{
m_countMatchPattern = constructionContext.createMatchPattern(atts.getValue(i), *this);
}
else if(equals(aname, Constants::ATTRNAME_FROM))
{
m_fromMatchPattern = constructionContext.createMatchPattern(atts.getValue(i), *this);
}
else if(equals(aname, Constants::ATTRNAME_VALUE))
{
m_valueExpr = constructionContext.createXPath(atts.getValue(i), *this);
}
else if(equals(aname, Constants::ATTRNAME_FORMAT))
{
m_format_avt = atts.getValue(i);
}
else if(equals(aname, Constants::ATTRNAME_LANG))
{
m_lang_avt = atts.getValue(i);
}
else if(equals(aname, Constants::ATTRNAME_LETTERVALUE))
{
constructionContext.warn(Constants::ATTRNAME_LETTERVALUE + " not supported yet!");
m_lettervalue_avt = atts.getValue(i);
}
else if(equals(aname,Constants::ATTRNAME_GROUPINGSEPARATOR))
{
m_groupingSeparator_avt = atts.getValue(i);
}
else if(equals(aname,Constants::ATTRNAME_GROUPINGSIZE))
{
m_groupingSize_avt = atts.getValue(i);
}
else if(!isAttrOK(aname, atts, i, constructionContext))
{
constructionContext.error(name + " has an illegal attribute: " + aname);
}
}
}
int
ElemNumber::getXSLToken() const
{
return Constants::ELEMNAME_NUMBER;
}
void ElemNumber::execute(
StylesheetExecutionContext& executionContext,
const DOM_Node& sourceTree,
const DOM_Node& sourceNode,
const QName& mode) const
{
ElemTemplateElement::execute(executionContext, sourceTree, sourceNode, mode);
DOMString countString = getCountString(executionContext, sourceTree, sourceNode);
if (!isEmpty(countString))
{
executionContext.characters(toCharArray(countString), 0, length(countString));
}
}
/**
* Add a child to the child list.
*/
NodeImpl* ElemNumber::appendChild(NodeImpl* newChild)
{
error("Can not add " + dynamic_cast<ElemTemplateElement*>(newChild)->getTagName() + " to " + this->getTagName());
return 0;
}
/**
* Given a 'from' pattern (ala xsl:number), a match pattern
* and a context, find the first ancestor that matches the
* pattern (including the context handed in).
* @param matchPatternString The match pattern.
* @param node The node that "." expresses.
* @param namespaceContext The context in which namespaces in the
* queries are supposed to be expanded.
*/
DOM_Node
ElemNumber::findAncestor(
StylesheetExecutionContext& executionContext,
const XPath* fromMatchPattern,
const XPath* countMatchPattern,
const DOM_Node& context,
const DOM_Element& /* namespaceContext */) const
{
DOM_Node contextCopy(context);
while(contextCopy != 0)
{
if(0 != fromMatchPattern)
{
if(fromMatchPattern->getMatchScore(contextCopy,
executionContext.getXPathExecutionContext()) !=
XPath::s_MatchScoreNone)
{
break;
}
}
if(0 != countMatchPattern)
{
if(countMatchPattern->getMatchScore(context,
executionContext.getXPathExecutionContext()) !=
XPath::s_MatchScoreNone)
{
break;
}
}
contextCopy = executionContext.getParentOfNode(contextCopy);
}
return contextCopy;
}
DOM_Node
ElemNumber::findPrecedingOrAncestorOrSelf(
StylesheetExecutionContext& executionContext,
const XPath* fromMatchPattern,
const XPath* countMatchPattern,
const DOM_Node& context,
const DOM_Element& /* namespaceContext */) const
{
DOM_Node contextCopy(context);
while(contextCopy != 0)
{
if(0 != fromMatchPattern)
{
if(fromMatchPattern->getMatchScore(contextCopy,
executionContext.getXPathExecutionContext()) != XPath::s_MatchScoreNone)
{
contextCopy = 0;
break;
}
}
if(0 != countMatchPattern)
{
if(countMatchPattern->getMatchScore(contextCopy,
executionContext.getXPathExecutionContext()) != XPath::s_MatchScoreNone)
{
break;
}
}
DOM_Node prevSibling = contextCopy.getPreviousSibling();
if(prevSibling == 0)
{
contextCopy = executionContext.getParentOfNode(contextCopy);
}
else
{
contextCopy = prevSibling;
}
}
return contextCopy;
}
/**
* Get the count match pattern, or a default value.
*/
const XPath*
ElemNumber::getCountMatchPattern(
StylesheetExecutionContext& executionContext,
const DOM_Node& contextNode) const
{
const XPath* countMatchPattern = m_countMatchPattern;
if(0 == countMatchPattern)
{
switch(contextNode.getNodeType())
{
case DOM_Node::ELEMENT_NODE:
countMatchPattern = executionContext.createMatchPattern(contextNode.getNodeName(),
*this);
break;
case DOM_Node::ATTRIBUTE_NODE:
countMatchPattern = executionContext.createMatchPattern(DOMString("@") + contextNode.getNodeName(),
*this);
break;
case DOM_Node::CDATA_SECTION_NODE:
case DOM_Node::TEXT_NODE:
countMatchPattern = executionContext.createMatchPattern(DOMString("text()"),
*this);
break;
case DOM_Node::COMMENT_NODE:
countMatchPattern = executionContext.createMatchPattern(DOMString("comment()"),
*this);
break;
case DOM_Node::DOCUMENT_NODE:
countMatchPattern = executionContext.createMatchPattern(DOMString("/"),
*this);
break;
case DOM_Node::PROCESSING_INSTRUCTION_NODE:
countMatchPattern = executionContext.createMatchPattern(DOMString("pi(") +
contextNode.getNodeName() + DOMString(")"),
*this);
break;
default:
assert(false);
break;
}
}
return countMatchPattern;
}
DOMString
ElemNumber::getCountString(
StylesheetExecutionContext& executionContext,
const DOM_Node& /* sourceTree */,
const DOM_Node& sourceNode) const
{
IntArrayType numberList;
if(0 != m_valueExpr)
{
const XObject* const countObj =
m_valueExpr->execute(sourceNode,
*this,
executionContext.getXPathExecutionContext());
numberList.push_back(static_cast<int>(countObj->num()));
}
else
{
const XPath* const countMatchPattern =
getCountMatchPattern(executionContext, sourceNode);
if((Constants::NUMBERLEVEL_ANY == m_level) ||
(Constants::NUMBERLEVEL_SINGLE == m_level))
{
if(Constants::NUMBERLEVEL_SINGLE == m_level)
{
DOM_Node target = findAncestor(executionContext, m_fromMatchPattern,
countMatchPattern, sourceNode, DOM_UnimplementedElement(const_cast<ElemNumber*>(this)));
if(target == 0)
target = executionContext.getParentOfNode(sourceNode);
if(target != 0)
{
numberList.push_back(getSiblingNumber(executionContext, countMatchPattern, target));
}
else
{
executionContext.warn(DOMString("Warning: count attribute does not match an ancestor in xsl:number! Target = ")
+ sourceNode.getNodeName(),
sourceNode,
DOM_Node());
}
}
else // if NUMBERLEVEL_ANY
{
DOM_Node from;
if(0 != m_fromMatchPattern)
{
// @@ JMD: was as below, which looked wrong to me based on java code and the
// meaning of the arguments. The sad fact is that the number of tests we
// passed went DOWN after this change
// from = findPrecedingOrAncestorOrSelf(executionContext, 0, m_fromMatchPattern,
from = findPrecedingOrAncestorOrSelf(executionContext, m_fromMatchPattern, countMatchPattern,
sourceNode, DOM_UnimplementedElement(const_cast<ElemNumber*>(this)));
if(from == 0)
{
from = sourceNode;
}
}
else
{
from = sourceNode.getOwnerDocument();
}
DOM_Node fromPos = (from != sourceNode) ? getNextInTree(from, from) : from;
numberList.push_back(getNumberInTree(executionContext.getXPathExecutionContext(), countMatchPattern, fromPos, from, sourceNode, 0));
}
}
else // if NUMBERLEVEL_MULTI
{
numberList = getAncestorNumbers(executionContext, m_fromMatchPattern,
countMatchPattern, sourceNode);
}
}
return numberList.size() > 0 ? formatNumberList(executionContext, numberList, sourceNode) : DOMString();
}
DOM_Node
ElemNumber::getNextInTree(
const DOM_Node& pos,
const DOM_Node& from)
{
DOM_Node posCopy(pos);
DOM_Node nextNode(posCopy.getFirstChild());
while(nextNode == 0)
{
nextNode = posCopy.getNextSibling();
if(nextNode == 0)
{
posCopy = posCopy.getParentNode();
if(posCopy == from)
{
break;
}
}
}
return nextNode;
}
int
ElemNumber::getNumberInTree(
XPathExecutionContext& executionContext,
const XPath* countMatchPattern,
const DOM_Node& pos,
const DOM_Node& from,
const DOM_Node& target,
int countFrom) const
{
DOM_Node posCopy(pos);
int count = countFrom;
if(posCopy != 0)
{
do
{
if( (0 == countMatchPattern) ||
(countMatchPattern->getMatchScore(posCopy,
executionContext) != XPath::s_MatchScoreNone))
{
count++;
}
}
while(posCopy != target &&
(posCopy = getNextInTree(posCopy, from)) != 0);
}
return count;
}
int
ElemNumber::getSiblingNumber(
StylesheetExecutionContext& executionContext,
const XPath* countMatchPattern,
const DOM_Node& target) const
{
int number = 0;
const DOM_Node theParent = executionContext.getParentOfNode(target);
assert(theParent != 0);
// TODO: If target is an Attr, implement special handling.
DOM_NodeList siblings = theParent.getChildNodes();
if (siblings != 0)
{
const int nNodes = siblings.getLength();
for(int i = 0; i < nNodes; i++)
{
const DOM_Node child = siblings.item(i);
if(child == target)
{
number++; // always count the target
break;
}
else if(0 == countMatchPattern ||
countMatchPattern->getMatchScore(child,
executionContext.getXPathExecutionContext()) != XPath::s_MatchScoreNone)
{
number++;
}
}
}
return number;
}
int
ElemNumber::countMatchingAncestors(
StylesheetExecutionContext& executionContext,
const XPath* patterns,
const DOM_Node& node) const
{
int count = 0;
DOM_Node nodeCopy(node);
while(nodeCopy != 0)
{
if(0 != patterns)
{
if(patterns->getMatchScore(nodeCopy,
executionContext.getXPathExecutionContext()) != XPath::s_MatchScoreNone)
{
count++;
}
}
else
{
count++;
}
nodeCopy = executionContext.getParentOfNode(nodeCopy);
}
return count;
}
ElemNumber::IntArrayType
ElemNumber::getAncestorNumbers(
StylesheetExecutionContext& executionContext,
const XPath* fromMatchPattern,
const XPath* countMatchPattern,
const DOM_Node& node) const
{
DOM_Node nodeCopy(node);
const int nMatchingAncestors =
countMatchingAncestors(executionContext,
countMatchPattern,
nodeCopy);
IntArrayType counts(nMatchingAncestors);
if(nMatchingAncestors > 0)
{
int countIndex = counts.size() - 1; // position to put count into
while(nodeCopy != 0)
{
bool countIt = false;
if(0 != countMatchPattern)
{
if(countMatchPattern->getMatchScore(nodeCopy,
executionContext.getXPathExecutionContext()) != XPath::s_MatchScoreNone)
{
countIt = true;
}
}
else
{
countIt = true;
}
if(countIt == true)
{
DOM_Node target =
findAncestor(executionContext,
fromMatchPattern,
countMatchPattern,
nodeCopy,
DOM_UnimplementedElement(const_cast<ElemNumber*>(this)));
if(target == 0)
target = nodeCopy;
counts[countIndex] = getSiblingNumber(executionContext, countMatchPattern, target);
countIndex--;
}
nodeCopy = executionContext.getParentOfNode(nodeCopy);
} // end while
} // end if nMatchingAncestors > 0
return counts;
}
#if ! defined(__GNUC__)
std::locale
ElemNumber::getLocale(
StylesheetExecutionContext& /* executionContext */,
const DOM_Node& /* contextNode */) const
{
//TODO
return std::locale();
}
#endif
NumberFormat*
ElemNumber::getNumberFormatter(
StylesheetExecutionContext& executionContext,
const DOM_Node& contextNode) const
{
#if ! defined(__GNUC__)
std::locale loc = getLocale(executionContext, contextNode);
#endif
// Helper to format local specific numbers to strings.
std::auto_ptr<NumberFormat> formatter(new NumberFormat);
DOM_UnimplementedElement theNamespaceContext(const_cast<ElemNumber*>(this));
DOMString digitGroupSepValue = (!isEmpty(m_groupingSeparator_avt))
? executionContext.evaluateAttrVal(contextNode,
theNamespaceContext,
m_groupingSeparator_avt) :
DOMString();
DOMString nDigitsPerGroupValue = (!isEmpty(m_groupingSize_avt))
? executionContext.evaluateAttrVal(contextNode,
theNamespaceContext,
m_groupingSize_avt) :
DOMString();
// TODO: Handle digit-group attributes
if(!isEmpty(digitGroupSepValue) || !isEmpty(nDigitsPerGroupValue))
{
formatter->setGroupingUsed(true);
formatter->setGroupingSeparator(m_groupingSeparator_avt);
formatter->setGroupingSize(m_groupingSize_avt);
}
return formatter.release();
}
DOMString
ElemNumber::formatNumberList(
StylesheetExecutionContext& executionContext,
const IntArrayType& theList,
const DOM_Node& contextNode) const
{
const int nNumbers = theList.size();
XMLCh numberType('1');
int numberWidth = 1;
DOMString formattedNumber;
DOMString formatToken;
DOMString sepString(".");
DOMString lastSepString;
DOMString formatValue = !isEmpty(m_format_avt)
? executionContext.evaluateAttrVal(contextNode,
DOM_UnimplementedElement(const_cast<ElemNumber*>(this)),
m_format_avt)
: DOMString();
if(isEmpty(formatValue))
formatValue = DOMString("1");
NumeratorFormatter::NumberFormatStringTokenizer formatTokenizer(formatValue);
#if ! defined(__GNUC__)
std::locale loc = getLocale(executionContext, contextNode);
#endif
// Construct an array of tokens. We need to be able to check if the last
// token in non-alphabetic, in which case the penultimate non-alphabetic is
// the repeating separator
StringVectorType tokenVector;
while(formatTokenizer.hasMoreTokens())
tokenVector.push_back(formatTokenizer.nextToken());
// Get rid of the leading and trailing non-alphabetics, save for later
DOMString leaderStr;
DOMString trailerStr;
StringVectorTypeIterator it;
it = tokenVector.begin();
if(! isLetterOrDigit(charAt((*it), 0)))
{
leaderStr = *it;
tokenVector.erase(it);
}
it += tokenVector.size()-1;
if(! isLetterOrDigit(charAt((*it), 0)))
{
trailerStr = *it;
tokenVector.erase(it);
}
// Now we're left with a sequence of alpha,non-alpha tokens, format them
// with the corresponding entry in the format string, or the last one if no
// more matching ones
formattedNumber = leaderStr;
it = tokenVector.begin();
for(int i = 0; i < nNumbers; i++)
{
if (it != tokenVector.end())
{
assert(isLetterOrDigit(charAt((*it), 0)));
formatToken = *it++;
numberWidth = length(formatToken);
numberType = charAt(formatToken, numberWidth - 1);
}
if (it != tokenVector.end())
{
assert(!isLetterOrDigit(charAt((*it), 0)));
sepString = *it++;
}
formattedNumber += getFormattedNumber(executionContext, contextNode,
numberType, numberWidth, theList[i]);
// All but the last one
if (i < nNumbers-1)
formattedNumber += sepString;
}
formattedNumber += trailerStr;
return formattedNumber;
}
DOMString
ElemNumber::getFormattedNumber(
StylesheetExecutionContext& executionContext,
const DOM_Node& contextNode,
XMLCh numberType,
int numberWidth,
int listElement) const
{
std::auto_ptr<NumberFormat> formatter(getNumberFormatter(executionContext, contextNode));
DOMString padString = formatter->format(0);
DOMString lookahead;
DOMString formattedNumber;
switch(numberType)
{
case 'A':
formattedNumber += int2alphaCount(listElement, s_alphaCountTable);
break;
case 'a':
formattedNumber += toLowerCase(int2alphaCount(listElement, s_alphaCountTable));
break;
case 'I':
formattedNumber += long2roman(listElement, true);
break;
case 'i':
formattedNumber += toLowerCase(long2roman(listElement, true));
break;
default: // "1"
{
const DOMString numString =
formatter->format(listElement);
const int nPadding = numberWidth - length(numString);
for(int i = 0; i < nPadding; i++)
{
formattedNumber += padString;
}
formattedNumber += numString;
}
break;
}
return formattedNumber;
}
/**
* Convert a long integer into alphabetic counting, in other words
* count using the sequence A B C ... Z AA AB AC.... etc.
* @param val Value to convert -- must be greater than zero.
* @param table a table containing one character for each digit in the radix
* @return String representing alpha count of number.
* @see XSLTEngineImpl#DecimalToRoman
*
* Note that the radix of the conversion is inferred from the size
* of the table.
*/
DOMString
ElemNumber::int2alphaCount(
int val,
const DOMString& table)
{
const int radix = length(table);
// Create a buffer to hold the result
// TODO: size of the table can be determined by computing
// logs of the radix. For now, we fake it.
const int buflen = 100;
std::vector<XMLCh> buf(buflen + 1, (XMLCh)0);
// next character to set in the buffer
int charPos = buflen - 1 ; // work backward through buf[]
// index in table of the last character that we stored
int lookupIndex = 1; // start off with anything other than zero to make correction work
// Correction number
//
// Correction can take on exactly two values:
//
// 0 if the next character is to be emitted is usual
//
// radix - 1
// if the next char to be emitted should be one less than
// you would expect
//
// For example, consider radix 10, where 1="A" and 10="J"
//
// In this scheme, we count: A, B, C ... H, I, J (not A0 and certainly
// not AJ), A1
//
// So, how do we keep from emitting AJ for 10? After correctly emitting the
// J, lookupIndex is zero. We now compute a correction number of 9 (radix-1).
// In the following line, we'll compute (val+correction) % radix, which is,
// (val+9)/10. By this time, val is 1, so we compute (1+9) % 10, which
// is 10 % 10 or zero. So, we'll prepare to emit "JJ", but then we'll
// later suppress the leading J as representing zero (in the mod system,
// it can represent either 10 or zero). In summary, the correction value of
// "radix-1" acts like "-1" when run through the mod operator, but with the
// desireable characteristic that it never produces a negative number.
int correction = 0;
// TODO: throw error on out of range input
do
{
// most of the correction calculation is explained above, the reason for the
// term after the "|| " is that it correctly propagates carries across
// multiple columns.
correction = ((lookupIndex == 0) ||
(correction != 0 && lookupIndex == radix-1 )) ? (radix-1) : 0;
// index in "table" of the next char to emit
lookupIndex = (val + correction) % radix;
// shift input by one "column"
val = (val / radix);
// if the next value we'd put out would be a leading zero, we're done.
if (lookupIndex == 0 && val == 0)
break;
// put out the next character of output
buf[charPos--] = static_cast<char>(charAt(table, lookupIndex));
}
while (val > 0);
DOMString retStr(buf.begin() + charPos + 1, (buflen - charPos - 1));
return retStr;
}
DOMString
ElemNumber::long2roman(
long val,
bool prefixesAreOK)
{
if(val <= 0)
{
return DOMString( "#E(" + LongToDOMString(val) +")" );
}
DOMString roman;
int place = 0;
if (val <= 3999L)
{
do
{
while (val >= s_romanConvertTable[place].m_postValue)
{
roman += s_romanConvertTable[place].m_postLetter;
val -= s_romanConvertTable[place].m_postValue;
}
if (prefixesAreOK)
{
if (val >= s_romanConvertTable[place].m_preValue)
{
roman += s_romanConvertTable[place].m_preLetter;
val -= s_romanConvertTable[place].m_preValue;
}
}
place++;
}
while (val > 0);
}
else
{
roman = "#error";
}
return roman;
}