net.sf.saxon.expr.NumberSequenceFormatter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon.expr;
import net.sf.saxon.Configuration;
import net.sf.saxon.expr.instruct.NumberInstruction;
import net.sf.saxon.expr.number.NumberFormatter;
import net.sf.saxon.expr.parser.ContextItemStaticInfo;
import net.sf.saxon.expr.parser.ExpressionTool;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.expr.parser.RebindingMap;
import net.sf.saxon.functions.Number_1;
import net.sf.saxon.lib.ConversionRules;
import net.sf.saxon.lib.Numberer;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.str.StringView;
import net.sf.saxon.str.UnicodeString;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* This expression performs the formatting part of the logic of the xsl:number instruction
* It takes as input a sequence of integers, which may either be supplied directly as the
* value attribute of xsl:number, or may be computed by counting nodes. The expression
* returns a string.
*/
public class NumberSequenceFormatter extends Expression {
private Operand valueOp;
private Operand formatOp;
private Operand groupSizeOp;
private Operand groupSeparatorOp;
private Operand letterValueOp;
private Operand ordinalOp;
private final Operand startAtOp;
private Operand langOp;
private NumberFormatter formatter = null;
private Numberer numberer = null;
private final boolean backwardsCompatible;
/**
* Construct a NumberSequenceFormatter
*
* @param value the expression supplied in the value attribute, or
* the {@link NumberInstruction} which computes the place-marker
* @param format the expression supplied in the format attribute
* @param groupSize the expression supplied in the group-size attribute
* @param groupSeparator the expression supplied in the grouping-separator attribute
* @param letterValue the expression supplied in the letter-value attribute
* @param ordinal the expression supplied in the ordinal attribute
* @param startAt the expression supplied in the start-at attribute
* @param lang the expression supplied in the lang attribute
* @param formatter A NumberFormatter to be used
* @param backwardsCompatible true if running in 1.0 compatibility mode with a real value attribute
*/
public NumberSequenceFormatter(Expression value,
Expression format,
Expression groupSize,
Expression groupSeparator,
Expression letterValue,
Expression ordinal,
Expression startAt,
Expression lang,
NumberFormatter formatter,
boolean backwardsCompatible) {
if (value != null) {
valueOp = new Operand(this, value, OperandRole.SINGLE_ATOMIC);
}
if (format != null) {
formatOp = new Operand(this, format, OperandRole.SINGLE_ATOMIC);
}
if (groupSize != null) {
groupSizeOp = new Operand(this, groupSize, OperandRole.SINGLE_ATOMIC);
}
if (groupSeparator != null) {
groupSeparatorOp = new Operand(this, groupSeparator, OperandRole.SINGLE_ATOMIC);
}
if (letterValue != null) {
letterValueOp = new Operand(this, letterValue, OperandRole.SINGLE_ATOMIC);
}
if (ordinal != null) {
ordinalOp = new Operand(this, ordinal, OperandRole.SINGLE_ATOMIC);
}
//if (startAt != null) {
startAtOp = new Operand(this, startAt, OperandRole.SINGLE_ATOMIC);
//}
if (lang != null) {
langOp = new Operand(this, lang, OperandRole.SINGLE_ATOMIC);
}
this.formatter = formatter;
this.backwardsCompatible = backwardsCompatible;
if (formatter == null && format instanceof StringLiteral) {
this.formatter = new NumberFormatter();
this.formatter.prepare(((StringLiteral)format).stringify());
}
}
/**
* Simplify an expression. This performs any static optimization (by rewriting the expression
* as a different expression). The default implementation simplifies its operands.
*
* @return the simplified expression (or the original if unchanged, or if modified in-situ)
* @throws XPathException if an error is discovered during expression
* rewriting
*/
@Override
public Expression simplify() throws XPathException {
if (valueOp != null && !valueOp.getChildExpression().getItemType().isPlainType()) {
valueOp.setChildExpression(Atomizer.makeAtomizer(valueOp.getChildExpression(), null));
}
preallocateNumberer(getConfiguration());
return super.simplify();
}
public void preallocateNumberer(Configuration config) throws XPathException {
if (langOp == null) {
numberer = config.makeNumberer(null, null);
} else {
if (langOp.getChildExpression() instanceof StringLiteral) {
String language = ((StringLiteral) langOp.getChildExpression()).stringify();
if (!language.isEmpty()) {
ValidationFailure vf = StringConverter.StringToLanguage.INSTANCE.validate(StringView.tidy(language));
if (vf != null) {
langOp.setChildExpression(new StringLiteral(StringValue.EMPTY_STRING));
throw new XPathException("The lang attribute must be a valid language code", "XTDE0030");
}
}
numberer = config.makeNumberer(language, null);
} // else we allocate a numberer at run-time
}
}
@Override
public Iterable operands() {
return operandSparseList(valueOp, formatOp, groupSizeOp,
groupSeparatorOp, letterValueOp, ordinalOp, startAtOp, langOp);
}
private boolean isFixed(Operand op) {
return op==null || op.getChildExpression() instanceof Literal;
}
private boolean hasFixedOperands() {
for (Operand o : operands()) {
if (!isFixed(o)) {
return false;
}
}
return true;
}
@Override
public Expression optimize(ExpressionVisitor visitor, ContextItemStaticInfo contextInfo) throws XPathException {
optimizeChildren(visitor, contextInfo);
if (hasFixedOperands()) {
StringValue val = evaluateItem(visitor.makeDynamicContext());
StringLiteral literal = new StringLiteral(val);
ExpressionTool.copyLocationInfo(this, literal);
return literal;
} else {
return this;
}
}
/**
* Copy an expression. This makes a deep copy.
*
* @return the copy of the original expression
* @param rebindings variables that need to be re-bound
*/
/*@NotNull*/
@Override
public Expression copy(RebindingMap rebindings) {
NumberSequenceFormatter exp = new NumberSequenceFormatter(
copy(valueOp, rebindings), copy(formatOp, rebindings),
copy(groupSizeOp, rebindings), copy(groupSeparatorOp, rebindings), copy(letterValueOp, rebindings),
copy(ordinalOp, rebindings), copy(startAtOp, rebindings),
copy(langOp, rebindings), formatter, backwardsCompatible);
ExpressionTool.copyLocationInfo(this, exp);
return exp;
}
private Expression copy(Operand op, RebindingMap rebindings) {
return op == null ? null : op.getChildExpression().copy(rebindings);
}
/*@NotNull*/
@Override
public ItemType getItemType() {
return BuiltInAtomicType.STRING;
}
@Override
protected int computeCardinality() {
return StaticProperty.EXACTLY_ONE;
}
/**
* An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process().
* This method indicates which of these methods is provided directly. The other methods will always be available
* indirectly, using an implementation that relies on one of the other methods.
*
* @return the implementation method, for example {@link #ITERATE_METHOD} or {@link #EVALUATE_METHOD} or
* {@link #PROCESS_METHOD}
*/
@Override
public int getImplementationMethod() {
return EVALUATE_METHOD;
}
@Override
public StringValue evaluateItem(XPathContext context) throws XPathException {
List