KHtml

predicate.cpp
1 /*
2  * predicate.cc - Copyright 2005 Frerich Raabe <[email protected]>
3  * Copyright 2010 Maksim Orlovich <[email protected]>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in the
13  * documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 #include "predicate.h"
27 #include "functions.h"
28 
29 #include <QString>
30 
31 #include "xml/dom_nodeimpl.h"
32 #include "xml/dom_nodelistimpl.h"
33 #include "kjs/operations.h"
34 #include "kjs/value.h"
35 
36 #include <math.h>
37 
38 using namespace DOM;
39 using namespace khtml;
40 using namespace khtml::XPath;
41 
42 Number::Number(double value)
43  : m_value(value)
44 {
45 }
46 
47 bool Number::isConstant() const
48 {
49  return true;
50 }
51 
52 QString Number::dump() const
53 {
54  return "<number>" + QString::number(m_value) + "</number>";
55 }
56 
57 Value Number::doEvaluate() const
58 {
59  return Value(m_value);
60 }
61 
62 String::String(const DOMString &value)
63  : m_value(value)
64 {
65 }
66 
67 bool String::isConstant() const
68 {
69  return true;
70 }
71 
72 QString String::dump() const
73 {
74  return "<string>" + m_value.string() + "</string>";
75 }
76 
77 Value String::doEvaluate() const
78 {
79  return Value(m_value);
80 }
81 
82 Value Negative::doEvaluate() const
83 {
84  Value p(subExpr(0)->evaluate());
85  return Value(-p.toNumber());
86 }
87 
88 QString Negative::dump() const
89 {
90  return "<negative>" + subExpr(0)->dump() + "</number>";
91 }
92 
93 QString BinaryExprBase::dump() const
94 {
95  QString s = "<" + opName() + ">";
96  s += "<operand>" + subExpr(0)->dump() + "</operand>";
97  s += "<operand>" + subExpr(1)->dump() + "</operand>";
98  s += "</" + opName() + ">";
99  return s;
100 }
101 
102 NumericOp::NumericOp(int _opCode, Expression *lhs, Expression *rhs) :
103  opCode(_opCode)
104 {
105  addSubExpression(lhs);
106  addSubExpression(rhs);
107 }
108 
109 Value NumericOp::doEvaluate() const
110 {
111  Value lhs(subExpr(0)->evaluate());
112  Value rhs(subExpr(1)->evaluate());
113  double leftVal = lhs.toNumber(), rightVal = rhs.toNumber();
114 
115  switch (opCode) {
116  case OP_Add:
117  return Value(leftVal + rightVal);
118  case OP_Sub:
119  return Value(leftVal - rightVal);
120  case OP_Mul:
121  return Value(leftVal * rightVal);
122  case OP_Div:
123  if (rightVal == 0.0 || rightVal == -0.0) {
124  if (leftVal == 0.0 || leftVal == -0.0) {
125  return Value(); // 0/0 = NaN
126  } else {
127  // +/- Infinity.
128  using namespace std; // for signbit
129  if (signbit(leftVal) == signbit(rightVal)) {
130  return Value(KJS::Inf);
131  } else {
132  return Value(-KJS::Inf);
133  }
134  }
135  } else {
136  return Value(leftVal / rightVal);
137  }
138  case OP_Mod:
139  if (rightVal == 0.0 || rightVal == -0.0) {
140  return Value(); //Divide by 0;
141  } else {
142  return Value(remainder(leftVal, rightVal));
143  }
144  default:
145  assert(0);
146  return Value();
147  }
148 }
149 
150 QString NumericOp::opName() const
151 {
152  switch (opCode) {
153  case OP_Add:
154  return QLatin1String("addition");
155  case OP_Sub:
156  return QLatin1String("subtraction");
157  case OP_Mul:
158  return QLatin1String("multiplication");
159  case OP_Div:
160  return QLatin1String("division");
161  case OP_Mod:
162  return QLatin1String("modulo");
163  default:
164  assert(0);
165  return QString();
166  }
167 }
168 
169 RelationOp::RelationOp(int _opCode, Expression *lhs, Expression *rhs) :
170  opCode(_opCode)
171 {
172  addSubExpression(lhs);
173  addSubExpression(rhs);
174 }
175 
176 static void stringify(const Value &val, WTF::Vector<DOMString> *out)
177 {
178  if (val.isString()) {
179  out->append(val.toString());
180  } else {
181  assert(val.isNodeset());
182 
183  const DomNodeList &set = val.toNodeset();
184  for (unsigned long i = 0; i < set->length(); ++i) {
185  DOM::DOMString stringVal = stringValue(set->item(i));
186  out->append(stringVal);
187  }
188  }
189 }
190 
191 static void numify(const Value &val, WTF::Vector<double> *out)
192 {
193  if (val.isNumber()) {
194  out->append(val.toNumber());
195  } else {
196  assert(val.isNodeset());
197 
198  const DomNodeList &set = val.toNodeset();
199  for (unsigned long i = 0; i < set->length(); ++i) {
200  DOM::DOMString stringVal = stringValue(set->item(i));
201  out->append(Value(stringVal).toNumber());
202  }
203  }
204 }
205 
206 Value RelationOp::doEvaluate() const
207 {
208  Value lhs(subExpr(0)->evaluate());
209  Value rhs(subExpr(1)->evaluate());
210 
211  if (lhs.isNodeset() || rhs.isNodeset()) {
212  // If both are nodesets, or one is a string our
213  // comparisons are based on strings.
214  if ((lhs.isNodeset() && rhs.isNodeset()) ||
215  (lhs.isString() || rhs.isString())) {
216 
217  WTF::Vector<DOM::DOMString> leftStrings;
218  WTF::Vector<DOM::DOMString> rightStrings;
219 
220  stringify(lhs, &leftStrings);
221  stringify(rhs, &rightStrings);
222 
223  for (unsigned pl = 0; pl < leftStrings.size(); ++pl) {
224  for (unsigned pr = 0; pr < rightStrings.size(); ++pr) {
225  if (compareStrings(leftStrings[pl], rightStrings[pr])) {
226  return Value(true);
227  }
228  } // pr
229  } // pl
230  return Value(false);
231  }
232 
233  // If one is a number, we do a number-based comparison
234  if (lhs.isNumber() || rhs.isNumber()) {
235  WTF::Vector<double> leftNums;
236  WTF::Vector<double> rightNums;
237 
238  numify(lhs, &leftNums);
239  numify(rhs, &rightNums);
240 
241  for (unsigned pl = 0; pl < leftNums.size(); ++pl) {
242  for (unsigned pr = 0; pr < rightNums.size(); ++pr) {
243  if (compareNumbers(leftNums[pl], rightNums[pr])) {
244  return Value(true);
245  }
246  } // pr
247  } // pl
248  return Value(false);
249  }
250 
251  // Has to be a boolean-based comparison.
252  // These ones are simpler, since we just convert the nodeset to a bool
253  assert(lhs.isBoolean() || rhs.isBoolean());
254 
255  if (lhs.isNodeset()) {
256  lhs = Value(lhs.toBoolean());
257  } else {
258  rhs = Value(rhs.toBoolean());
259  }
260  } // nodeset comparisons
261 
262  if (opCode == OP_EQ || opCode == OP_NE) {
263  bool equal;
264  if (lhs.isBoolean() || rhs.isBoolean()) {
265  equal = (lhs.toBoolean() == rhs.toBoolean());
266  } else if (lhs.isNumber() || rhs.isNumber()) {
267  equal = (lhs.toNumber() == rhs.toNumber());
268  } else {
269  equal = (lhs.toString() == rhs.toString());
270  }
271 
272  if (opCode == OP_EQ) {
273  return Value(equal);
274  } else {
275  return Value(!equal);
276  }
277 
278  }
279 
280  // For other ops, we always convert to numbers
281  double leftVal = lhs.toNumber(), rightVal = rhs.toNumber();
282  return Value(compareNumbers(leftVal, rightVal));
283 }
284 
285 bool RelationOp::compareNumbers(double leftVal, double rightVal) const
286 {
287  switch (opCode) {
288  case OP_GT:
289  return leftVal > rightVal;
290  case OP_GE:
291  return leftVal >= rightVal;
292  case OP_LT:
293  return leftVal < rightVal;
294  case OP_LE:
295  return leftVal <= rightVal;
296  case OP_EQ:
297  return leftVal == rightVal;
298  case OP_NE:
299  return leftVal != rightVal;
300  default:
301  assert(0);
302  return false;
303  }
304 }
305 
306 bool RelationOp::compareStrings(const DOM::DOMString &l, const DOM::DOMString &r) const
307 {
308  // String comparisons, as invoked within the nodeset case, are string-based
309  // only for == and !=. Everything else still goes to numbers.
310  if (opCode == OP_EQ) {
311  return (l == r);
312  }
313  if (opCode == OP_NE) {
314  return (l != r);
315  }
316 
317  return compareNumbers(Value(l).toNumber(), Value(r).toNumber());
318 }
319 
320 QString RelationOp::opName() const
321 {
322  switch (opCode) {
323  case OP_GT:
324  return QLatin1String("relationGT");
325  case OP_GE:
326  return QLatin1String("relationGE");
327  case OP_LT:
328  return QLatin1String("relationLT");
329  case OP_LE:
330  return QLatin1String("relationLE");
331  case OP_EQ:
332  return QLatin1String("relationEQ");
333  case OP_NE:
334  return QLatin1String("relationNE");
335  default:
336  assert(0);
337  return QString();
338  }
339 }
340 
341 LogicalOp::LogicalOp(int _opCode, Expression *lhs, Expression *rhs) :
342  opCode(_opCode)
343 {
344  addSubExpression(lhs);
345  addSubExpression(rhs);
346 }
347 
348 bool LogicalOp::shortCircuitOn() const
349 {
350  if (opCode == OP_And) {
351  return false; //false and foo
352  } else {
353  return true; //true or bar
354  }
355 }
356 
357 bool LogicalOp::isConstant() const
358 {
359  return subExpr(0)->isConstant() &&
360  subExpr(0)->evaluate().toBoolean() == shortCircuitOn();
361 }
362 
363 QString LogicalOp::opName() const
364 {
365  if (opCode == OP_And) {
366  return QLatin1String("conjunction");
367  } else {
368  return QLatin1String("disjunction");
369  }
370 }
371 
372 Value LogicalOp::doEvaluate() const
373 {
374  Value lhs(subExpr(0)->evaluate());
375 
376  // This is not only an optimization, https://www.w3.org/TR/xpath
377  // dictates that we must do short-circuit evaluation
378  bool lhsBool = lhs.toBoolean();
379  if (lhsBool == shortCircuitOn()) {
380  return Value(lhsBool);
381  }
382 
383  return Value(subExpr(1)->evaluate().toBoolean());
384 }
385 
386 QString Union::opName() const
387 {
388  return QLatin1String("union");
389 }
390 
391 Value Union::doEvaluate() const
392 {
393  Value lhs = subExpr(0)->evaluate();
394  Value rhs = subExpr(1)->evaluate();
395  if (!lhs.isNodeset() || !rhs.isNodeset()) {
396  qCWarning(KHTML_LOG) << "Union operator '|' works only with nodesets.";
397  Expression::reportInvalidExpressionErr();
398  return Value(new StaticNodeListImpl);
399  }
400 
401  DomNodeList lhsNodes = lhs.toNodeset();
402  DomNodeList rhsNodes = rhs.toNodeset();
403  DomNodeList result = new StaticNodeListImpl;
404 
405  for (unsigned long n = 0; n < lhsNodes->length(); ++n) {
406  result->append(lhsNodes->item(n));
407  }
408 
409  for (unsigned long n = 0; n < rhsNodes->length(); ++n) {
410  result->append(rhsNodes->item(n));
411  }
412 
413  return Value(result);
414 }
415 
416 Predicate::Predicate(Expression *expr)
417  : m_expr(expr)
418 {
419 }
420 
421 Predicate::~Predicate()
422 {
423  delete m_expr;
424 }
425 
426 bool Predicate::evaluate() const
427 {
428  Q_ASSERT(m_expr != nullptr);
429 
430  Value result(m_expr->evaluate());
431 
432  // foo[3] really means foo[position()=3]
433  if (result.isNumber()) {
434  return double(Expression::evaluationContext().position) == result.toNumber();
435  }
436 
437  return result.toBoolean();
438 }
439 
440 void Predicate::optimize()
441 {
442  m_expr->optimize();
443 }
444 
445 QString Predicate::dump() const
446 {
447  return QString() + "<predicate>" + m_expr->dump() + "</predicate>";
448 }
449 
This file is part of the HTML rendering engine for KDE.
QString number(int n, int base)
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
This library provides a full-featured HTML parser and widget.
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:06 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.