All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.saxon.expr.instruct.ValueOf Maven / Gradle / Ivy

There is a newer version: 12.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.instruct;

import net.sf.saxon.Controller;
import net.sf.saxon.event.Outputter;
import net.sf.saxon.event.ProxyOutputter;
import net.sf.saxon.event.ReceiverOption;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.*;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.s9api.Location;
import net.sf.saxon.str.*;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.transpile.CSharpInnerClass;
import net.sf.saxon.transpile.CSharpModifiers;
import net.sf.saxon.tree.util.Orphan;
import net.sf.saxon.type.*;
import net.sf.saxon.value.Cardinality;
import net.sf.saxon.value.Whitespace;
import net.sf.saxon.z.IntIterator;

import java.util.function.BiConsumer;

/**
 * An xsl:value-of element in the stylesheet. 
* The xsl:value-of element takes attributes:
    *
  • a mandatory attribute select="expression". * This must be a valid String expression
  • *
  • an optional disable-output-escaping attribute, value "yes" or "no"
  • *
  • an optional separator attribute. This is handled at compile-time: if the separator attribute * is present, the select expression passed in here will be a call to the string-join() function.
  • *
*/ public final class ValueOf extends SimpleNodeConstructor { private int options; private boolean numberingInstruction = false; // set to true if generated by xsl:number private final boolean noNodeIfEmpty; /** * Create a new ValueOf expression * * @param select the select expression * @param disable true if disable-output-escaping is in force * @param noNodeIfEmpty true if the instruction is to return () if the select expression is (), * false if it is to return an empty text node */ public ValueOf(Expression select, boolean disable, boolean noNodeIfEmpty) { setSelect(select); options = disable ? ReceiverOption.DISABLE_ESCAPING : ReceiverOption.NONE; this.noNodeIfEmpty = noNodeIfEmpty; adoptChildExpression(select); // If value is fixed, test whether there are any special characters that might need to be // escaped when the time comes for serialization if (select instanceof StringLiteral) { boolean special = false; UnicodeString val = ((StringLiteral) select).getString(); IntIterator iter = val.codePoints(); while (iter.hasNext()) { int c = iter.next(); if (c < 33 || c > 126 || c == '<' || c == '>' || c == '&') { special = true; break; } } if (!special) { options |= ReceiverOption.NO_SPECIAL_CHARS; } } } /** * Indicate that this is really an xsl:nunber instruction */ public void setIsNumberingInstruction() { numberingInstruction = true; } /** * Determine whether this is really an xsl:number instruction * * @return true if this derives from xsl:number */ public boolean isNumberingInstruction() { return numberingInstruction; } public boolean isNoNodeIfEmpty() { return noNodeIfEmpty; } @Override public String toShortString() { if (getSelect() instanceof StringLiteral) { return "text{" + Err.depict(((StringLiteral)getSelect()).getGroundedValue()) + "}"; } else { return super.toShortString(); } } /** * Get the properties of this object to be included in trace messages, by supplying * the property values to a supplied consumer function * * @param consumer the function to which the properties should be supplied, as (property name, * value) pairs. */ @Override public void gatherProperties(BiConsumer consumer) { if (getSelect() instanceof StringLiteral) { consumer.accept("text", (((StringLiteral) getSelect()).getGroundedValue().getUnicodeStringValue())); } } /** * Get the name of this instruction for diagnostic and tracing purposes * * @return the namecode of the instruction name */ @Override public int getInstructionNameCode() { if (numberingInstruction) { return StandardNames.XSL_NUMBER; } else if (getSelect() instanceof StringLiteral) { return StandardNames.XSL_TEXT; } else { return StandardNames.XSL_VALUE_OF; } } /** * Test for any special options such as disable-output-escaping * * @return any special options */ public int getOptions() { return options; } /** * Test whether disable-output-escaping was requested * * @return true if disable-output-escaping was requested */ public boolean isDisableOutputEscaping() { return ReceiverOption.contains(options, ReceiverOption.DISABLE_ESCAPING); } /*@NotNull*/ @Override public ItemType getItemType() { return NodeKindTest.TEXT; } @Override protected int computeCardinality() { if (noNodeIfEmpty) { return StaticProperty.ALLOWS_ZERO_OR_ONE; } else { return StaticProperty.EXACTLY_ONE; } } @Override public void localTypeCheck(ExpressionVisitor visitor, ContextItemStaticInfo contextItemType) { // } /** * Determine the intrinsic dependencies of an expression, that is, those which are not derived * from the dependencies of its subexpressions. For example, position() has an intrinsic dependency * on the context position, while (position()+1) does not. The default implementation * of the method returns 0, indicating "no dependencies". * * @return a set of bit-significant flags identifying the "intrinsic" * dependencies. The flags are documented in class net.sf.saxon.value.StaticProperty */ @Override public int getIntrinsicDependencies() { int d = super.getIntrinsicDependencies(); if (isDisableOutputEscaping()) { // Bug 2312 : prevent extraction of global variables d |= StaticProperty.DEPENDS_ON_ASSIGNABLE_GLOBALS; } return d; } /** * 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) { ValueOf exp = new ValueOf(getSelect().copy(rebindings), ReceiverOption.contains(options, ReceiverOption.DISABLE_ESCAPING), noNodeIfEmpty); ExpressionTool.copyLocationInfo(this, exp); if (numberingInstruction) { exp.setIsNumberingInstruction(); } return exp; } /** * Check statically that the results of the expression are capable of constructing the content * of a given schema type. * * @param parentType The schema type * @param whole true if this expression is to account for the whole value of the type * @throws net.sf.saxon.trans.XPathException * if the expression doesn't match the required content type */ @Override public void checkPermittedContents(SchemaType parentType, boolean whole) throws XPathException { // if the expression is a constant value, check that it is valid for the type if (getSelect() instanceof Literal) { GroundedValue selectValue = ((Literal) getSelect()).getGroundedValue(); SimpleType stype = null; if (parentType instanceof SimpleType && whole) { stype = (SimpleType) parentType; } else if (parentType instanceof ComplexType && ((ComplexType) parentType).isSimpleContent()) { stype = ((ComplexType) parentType).getSimpleContentType(); } if (whole && stype != null && !stype.isNamespaceSensitive()) { // Can't validate namespace-sensitive content statically ValidationFailure err = stype.validateContent( selectValue.getUnicodeStringValue(), null, getConfiguration().getConversionRules()); if (err != null) { err.setLocator(getLocation()); err.setErrorCode(isXSLT() ? "XTTE1540" : "XQDY0027"); throw err.makeException(); } return; } if (parentType instanceof ComplexType && !((ComplexType) parentType).isSimpleContent() && !((ComplexType) parentType).isMixedContent() && !Whitespace.isAllWhite(selectValue.getUnicodeStringValue())) { XPathException err = new XPathException("The containing element must be of type " + parentType.getDescription() + ", which does not allow text content " + Err.wrap(selectValue.getUnicodeStringValue())); err.setLocation(getLocation()); err.setIsTypeError(true); throw err; } } else { // check that the type allows text nodes. If not, this is a warning condition, since the text // node might turn out to be whitespace // DROPPED because we can't do env.issueWarning // if (parentType instanceof ComplexType && // !((ComplexType) parentType).isSimpleContent() && // !((ComplexType) parentType).isMixedContent()) { // env.issueWarning("The containing element must be of type " + parentType.getDescription() + // ", which does not allow text content other than whitespace", this); // } } } /** * Convert this value-of instruction to an expression that delivers the string-value of the resulting * text node as an untyped atomic value. * * @return the converted expression */ public Expression convertToCastAsString() { if (noNodeIfEmpty || !Cardinality.allowsZero(getSelect().getCardinality())) { return new CastExpression(getSelect(), BuiltInAtomicType.UNTYPED_ATOMIC, true); } else { // must return zero-length string rather than () if empty Expression sf = SystemFunction.makeCall("string", getRetainedStaticContext(), getSelect()); return new CastExpression(sf, BuiltInAtomicType.UNTYPED_ATOMIC, false); } } /** * Process this instruction * * * @param output the destination for the result * @param context the dynamic context of the transformation * @return a TailCall to be executed by the caller, always null for this instruction */ /*@Nullable*/ @Override @CSharpInnerClass(outer = true, extra = {"Saxon.Hej.s9api.Location instructionLoc"}) public TailCall processLeavingTail(Outputter output, XPathContext context) throws XPathException { if (noNodeIfEmpty) { Item value = getSelect().evaluateItem(context); if (value != null) { processValue(value.getUnicodeStringValue(), output, context); } return null; } else if (getSelect().getItemType() == BuiltInAtomicType.STRING && !isDisableOutputEscaping() && !Cardinality.allowsZero(getCardinality())) { // Try to stream the value direct to the serializer where possible // Note: see bug 4944, which is why we don't use this path if the select // expression might return an empty sequence. Location instructionLoc = getLocation(); Outputter toText = new ProxyOutputter(output) { @Override public void append(Item item) throws XPathException { getNextOutputter().characters(item.getUnicodeStringValue(), instructionLoc, options); } @Override public void append(Item item, Location loc, int properties) throws XPathException { Location location = loc.getLineNumber() == -1 ? instructionLoc : loc; getNextOutputter().characters(item.getUnicodeStringValue(), location, properties | options); } @Override @CSharpModifiers(code = {"public", "override"}) public UniStringConsumer getStringReceiver(boolean asTextNode, Location loc) { Location location = loc.getLineNumber() == -1 ? instructionLoc : loc; return getNextOutputter().getStringReceiver(true, location); } }; getSelect().process(toText, context); return null; } else { return super.processLeavingTail(output, context); } } /** * Process the value of the node, to create the new node. * * @param value the string value of the new node * @param output the destination for the result * @param context the dynamic evaluation context * @throws XPathException if a dynamic error occurs * */ @Override public void processValue(UnicodeString value, Outputter output, XPathContext context) throws XPathException { output.characters(value, getLocation(), options); } /** * Evaluate this expression, returning the resulting text node to the caller * * @param context the dynamic evaluation context * @return the parentless text node that results from evaluating this instruction, or null to * represent an empty sequence * @throws XPathException if a dynamic error occurs */ @Override public NodeInfo evaluateItem(XPathContext context) throws XPathException { try { UnicodeString val; Item item = getSelect().evaluateItem(context); if (item == null) { if (noNodeIfEmpty) { return null; } else { val = EmptyUnicodeString.getInstance(); } } else { val = item.getUnicodeStringValue(); } Controller controller = context.getController(); assert controller != null; Orphan o = new Orphan(controller.getConfiguration()); o.setNodeKind(Type.TEXT); o.setStringValue(val); if (isDisableOutputEscaping()) { o.setDisableOutputEscaping(true); } return o; } catch (XPathException err) { err.maybeSetLocation(getLocation()); throw err; } } /** * Diagnostic print of expression structure. The abstract expression tree * is written to the supplied output destination. */ @Override public void export(ExpressionPresenter out) throws XPathException { out.startElement("valueOf", this); String flags = ""; if (isDisableOutputEscaping()) { flags += "d"; } if (ReceiverOption.contains(options, ReceiverOption.NO_SPECIAL_CHARS)) { flags += "S"; } if (noNodeIfEmpty) { flags += "e"; } if (isLocal()) { flags += "l"; } if (!flags.isEmpty()) { out.emitAttribute("flags", flags); } getSelect().export(out); out.endElement(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy