001: package net.sf.saxon.instruct;
002:
003: import net.sf.saxon.expr.*;
004: import net.sf.saxon.functions.Matches;
005: import net.sf.saxon.om.Item;
006: import net.sf.saxon.om.NamePool;
007: import net.sf.saxon.pattern.NoNodeTest;
008: import net.sf.saxon.style.StandardNames;
009: import net.sf.saxon.trans.DynamicError;
010: import net.sf.saxon.trans.XPathException;
011: import net.sf.saxon.type.*;
012: import net.sf.saxon.value.SequenceType;
013:
014: import java.io.PrintStream;
015: import java.util.ArrayList;
016: import java.util.Iterator;
017: import java.util.regex.Pattern;
018: import java.util.regex.PatternSyntaxException;
019:
020: /**
021: * An xsl:analyze-string elements in the stylesheet. New at XSLT 2.0<BR>
022: */
023:
024: public class AnalyzeString extends Instruction {
025:
026: private Expression select;
027: private Expression regex;
028: private Expression flags;
029: private Expression matching;
030: private Expression nonMatching;
031: private Pattern pattern; // a regex pattern, not an XSLT pattern!
032:
033: /**
034: * Construct an AnalyzeString instruction
035: *
036: * @param select the expression containing the input string
037: * @param regex the regular expression
038: * @param flags the flags parameter
039: * @param matching actions to be applied to a matching substring
040: * @param nonMatching actions to be applied to a non-matching substring
041: * @param pattern the compiled regular expression, if it was known statically
042: */
043: public AnalyzeString(Expression select, Expression regex,
044: Expression flags, Expression matching,
045: Expression nonMatching, Pattern pattern) {
046: this .select = select;
047: this .regex = regex;
048: this .flags = flags;
049: this .matching = matching;
050: this .nonMatching = nonMatching;
051: this .pattern = pattern;
052:
053: Iterator kids = iterateSubExpressions();
054: while (kids.hasNext()) {
055: Expression child = (Expression) kids.next();
056: adoptChildExpression(child);
057: }
058:
059: }
060:
061: public int getInstructionNameCode() {
062: return StandardNames.XSL_ANALYZE_STRING;
063: }
064:
065: /**
066: * Get the expression used to process matching substrings
067: */
068:
069: public Expression getMatchingExpression() {
070: return matching;
071: }
072:
073: /**
074: * Get the expression used to process non-matching substrings
075: */
076:
077: public Expression getNonMatchingExpression() {
078: return nonMatching;
079: }
080:
081: /**
082: * Simplify an expression. This performs any static optimization (by rewriting the expression
083: * as a different expression).
084: *
085: * @return the simplified expression
086: * @throws XPathException if an error is discovered during expression
087: * rewriting
088: */
089:
090: public Expression simplify(StaticContext env) throws XPathException {
091: select = select.simplify(env);
092: regex = regex.simplify(env);
093: flags = flags.simplify(env);
094: if (matching != null) {
095: matching = matching.simplify(env);
096: }
097: if (nonMatching != null) {
098: nonMatching = nonMatching.simplify(env);
099: }
100: return this ;
101: }
102:
103: public Expression typeCheck(StaticContext env,
104: ItemType contextItemType) throws XPathException {
105: select = select.typeCheck(env, contextItemType);
106: adoptChildExpression(select);
107: regex = regex.typeCheck(env, contextItemType);
108: adoptChildExpression(regex);
109: flags = flags.typeCheck(env, contextItemType);
110: adoptChildExpression(flags);
111: if (matching != null) {
112: matching = matching.typeCheck(env, Type.STRING_TYPE);
113: adoptChildExpression(matching);
114: }
115: if (nonMatching != null) {
116: nonMatching = nonMatching.typeCheck(env, Type.STRING_TYPE);
117: adoptChildExpression(nonMatching);
118: }
119: // Following type checking has already been done in the case of XSLT xsl:analyze-string, but is
120: // needed where the instruction is generated from saxon:analyze-string extension function
121: RoleLocator role = new RoleLocator(RoleLocator.INSTRUCTION,
122: "analyze-string/select", 0, null);
123: role.setSourceLocator(this );
124: select = TypeChecker.staticTypeCheck(select,
125: SequenceType.SINGLE_STRING, false, role, env);
126:
127: role = new RoleLocator(RoleLocator.INSTRUCTION,
128: "analyze-string/regex", 0, null);
129: role.setSourceLocator(this );
130: regex = TypeChecker.staticTypeCheck(regex,
131: SequenceType.SINGLE_STRING, false, role, env);
132:
133: role = new RoleLocator(RoleLocator.INSTRUCTION,
134: "analyze-string/flags", 0, null);
135: role.setSourceLocator(this );
136: flags = TypeChecker.staticTypeCheck(flags,
137: SequenceType.SINGLE_STRING, false, role, env);
138:
139: return this ;
140: }
141:
142: public Expression optimize(Optimizer opt, StaticContext env,
143: ItemType contextItemType) throws XPathException {
144: select = select.optimize(opt, env, contextItemType);
145: adoptChildExpression(select);
146: regex = regex.optimize(opt, env, contextItemType);
147: adoptChildExpression(regex);
148: flags = flags.optimize(opt, env, contextItemType);
149: adoptChildExpression(flags);
150: if (matching != null) {
151: matching = matching.optimize(opt, env, Type.STRING_TYPE);
152: adoptChildExpression(matching);
153: }
154: if (nonMatching != null) {
155: nonMatching = nonMatching.optimize(opt, env,
156: Type.STRING_TYPE);
157: adoptChildExpression(nonMatching);
158: }
159: return this ;
160: }
161:
162: /**
163: * Check that any elements and attributes constructed or returned by this expression are acceptable
164: * in the content model of a given complex type. It's always OK to say yes, since the check will be
165: * repeated at run-time. The process of checking element and attribute constructors against the content
166: * model of a complex type also registers the type of content expected of those constructors, so the
167: * static validation can continue recursively.
168: */
169:
170: public void checkPermittedContents(SchemaType parentType,
171: StaticContext env, boolean whole) throws XPathException {
172: if (matching != null) {
173: matching.checkPermittedContents(parentType, env, false);
174: }
175: if (nonMatching != null) {
176: nonMatching.checkPermittedContents(parentType, env, false);
177: }
178: }
179:
180: /**
181: * Get the item type of the items returned by evaluating this instruction
182: *
183: * @return the static item type of the instruction
184: * @param th
185: */
186:
187: public ItemType getItemType(TypeHierarchy th) {
188: if (matching != null) {
189: if (nonMatching != null) {
190: return Type.getCommonSuperType(
191: matching.getItemType(th), nonMatching
192: .getItemType(th), th);
193: } else {
194: return matching.getItemType(th);
195: }
196: } else {
197: if (nonMatching != null) {
198: return nonMatching.getItemType(th);
199: } else {
200: return NoNodeTest.getInstance();
201: }
202: }
203: }
204:
205: /**
206: * Compute the dependencies of an expression, as the union of the
207: * dependencies of its subexpressions. (This is overridden for path expressions
208: * and filter expressions, where the dependencies of a subexpression are not all
209: * propogated). This method should be called only once, to compute the dependencies;
210: * after that, getDependencies should be used.
211: *
212: * @return the depencies, as a bit-mask
213: */
214:
215: public int computeDependencies() {
216: // some of the dependencies in the "action" part and in the grouping and sort keys aren't relevant,
217: // because they don't depend on values set outside the for-each-group expression
218: int dependencies = 0;
219: dependencies |= select.getDependencies();
220: dependencies |= regex.getDependencies();
221: dependencies |= flags.getDependencies();
222: if (matching != null) {
223: dependencies |= (matching.getDependencies() & ~(StaticProperty.DEPENDS_ON_FOCUS | StaticProperty.DEPENDS_ON_REGEX_GROUP));
224: }
225: if (nonMatching != null) {
226: dependencies |= (nonMatching.getDependencies() & ~(StaticProperty.DEPENDS_ON_FOCUS | StaticProperty.DEPENDS_ON_REGEX_GROUP));
227: }
228: return dependencies;
229: }
230:
231: /**
232: * Handle promotion offers, that is, non-local tree rewrites.
233: *
234: * @param offer The type of rewrite being offered
235: * @throws XPathException
236: */
237:
238: protected void promoteInst(PromotionOffer offer)
239: throws XPathException {
240: select = doPromotion(select, offer);
241: regex = doPromotion(regex, offer);
242: flags = doPromotion(flags, offer);
243: if (matching != null) {
244: matching = doPromotion(matching, offer);
245: }
246: if (nonMatching != null) {
247: nonMatching = doPromotion(nonMatching, offer);
248: }
249: }
250:
251: /**
252: * Get all the XPath expressions associated with this instruction
253: * (in XSLT terms, the expression present on attributes of the instruction,
254: * as distinct from the child instructions in a sequence construction)
255: */
256:
257: public Iterator iterateSubExpressions() {
258: ArrayList list = new ArrayList(5);
259: list.add(select);
260: list.add(regex);
261: list.add(flags);
262: if (matching != null) {
263: list.add(matching);
264: }
265: if (nonMatching != null) {
266: list.add(nonMatching);
267: }
268: return list.iterator();
269: }
270:
271: public TailCall processLeavingTail(XPathContext context)
272: throws XPathException {
273: RegexIterator iter = getRegexIterator(context);
274: XPathContextMajor c2 = context.newContext();
275: c2.setOrigin(this );
276: c2.setCurrentIterator(iter);
277: c2.setCurrentRegexIterator(iter);
278:
279: while (true) {
280: Item it = iter.next();
281: if (it == null) {
282: break;
283: }
284: if (iter.isMatching()) {
285: if (matching != null) {
286: matching.process(c2);
287: }
288: } else {
289: if (nonMatching != null) {
290: nonMatching.process(c2);
291: }
292: }
293: }
294:
295: return null;
296:
297: }
298:
299: /**
300: * Get an iterator over the substrings defined by the regular expression
301: *
302: * @param context the evaluation context
303: * @return an iterator that returns matching and nonmatching substrings
304: * @throws XPathException
305: */
306:
307: private RegexIterator getRegexIterator(XPathContext context)
308: throws XPathException {
309: String input = select.evaluateAsString(context);
310:
311: Pattern re = pattern;
312: if (re == null) {
313: int jflags = Matches.setFlags(flags
314: .evaluateAsString(context));
315: try {
316: String javaRegex = RegexTranslator.translate(regex
317: .evaluateAsString(context), true);
318: re = Pattern.compile(javaRegex, jflags);
319: } catch (RegexTranslator.RegexSyntaxException err) {
320: throw new DynamicError(err);
321: } catch (PatternSyntaxException err) {
322: throw new DynamicError(err);
323: }
324: }
325:
326: RegexIterator iter = new RegexIterator(input, re);
327: return iter;
328: }
329:
330: // TODO: implement an iterate() method (as a MappingIterator).
331:
332: /**
333: * Diagnostic print of expression structure. The expression is written to the System.err
334: * output stream
335: *
336: * @param level indentation level for this expression
337: * @param out
338: */
339:
340: public void display(int level, NamePool pool, PrintStream out) {
341: out.println(ExpressionTool.indent(level) + "analyze-string");
342: out.println(ExpressionTool.indent(level) + "select = ");
343: select.display(level + 1, pool, out);
344: out.println(ExpressionTool.indent(level) + "regex = ");
345: regex.display(level + 1, pool, out);
346: out.println(ExpressionTool.indent(level) + "flags = ");
347: flags.display(level + 1, pool, out);
348: if (matching != null) {
349: out.println(ExpressionTool.indent(level) + "matching = ");
350: matching.display(level + 1, pool, out);
351: }
352: if (nonMatching != null) {
353: out.println(ExpressionTool.indent(level)
354: + "non-matching = ");
355: nonMatching.display(level + 1, pool, out);
356: }
357: }
358: }
359:
360: //
361: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
362: // you may not use this file except in compliance with the License. You may obtain a copy of the
363: // License at http://www.mozilla.org/MPL/
364: //
365: // Software distributed under the License is distributed on an "AS IS" basis,
366: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
367: // See the License for the specific language governing rights and limitations under the License.
368: //
369: // The Original Code is: all this file.
370: //
371: // The Initial Developer of the Original Code is Michael H. Kay
372: //
373: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
374: //
375: // Contributor(s):
376: // Portions marked "e.g." are from Edwin Glaser (edwin@pannenleiter.de)
377: //
|