Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

OperandClassTransformer.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ss.formula;
  16. import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
  17. import org.apache.poi.ss.formula.ptg.AttrPtg;
  18. import org.apache.poi.ss.formula.ptg.ControlPtg;
  19. import org.apache.poi.ss.formula.ptg.FuncVarPtg;
  20. import org.apache.poi.ss.formula.ptg.IntersectionPtg;
  21. import org.apache.poi.ss.formula.ptg.MemAreaPtg;
  22. import org.apache.poi.ss.formula.ptg.MemFuncPtg;
  23. import org.apache.poi.ss.formula.ptg.Ptg;
  24. import org.apache.poi.ss.formula.ptg.RangePtg;
  25. import org.apache.poi.ss.formula.ptg.UnionPtg;
  26. import org.apache.poi.ss.formula.ptg.ValueOperatorPtg;
  27. import org.apache.poi.util.Removal;
  28. /**
  29. * This class performs 'operand class' transformation. Non-base tokens are classified into three
  30. * operand classes:
  31. * <ul>
  32. * <li>reference</li>
  33. * <li>value</li>
  34. * <li>array</li>
  35. * </ul>
  36. * <p/>
  37. *
  38. * The final operand class chosen for each token depends on the formula type and the token's place
  39. * in the formula. If POI gets the operand class wrong, Excel <em>may</em> interpret the formula
  40. * incorrectly. This condition is typically manifested as a formula cell that displays as '#VALUE!',
  41. * but resolves correctly when the user presses F2, enter.<p/>
  42. *
  43. * The logic implemented here was partially inspired by the description in
  44. * "OpenOffice.org's Documentation of the Microsoft Excel File Format". The model presented there
  45. * seems to be inconsistent with observed Excel behaviour (These differences have not been fully
  46. * investigated). The implementation in this class has been heavily modified in order to satisfy
  47. * concrete examples of how Excel performs the same logic (see TestRVA).<p/>
  48. *
  49. * Hopefully, as additional important test cases are identified and added to the test suite,
  50. * patterns might become more obvious in this code and allow for simplification.
  51. *
  52. * @author Josh Micich
  53. */
  54. final class OperandClassTransformer {
  55. private final FormulaType _formulaType;
  56. /**
  57. * @deprecated POI 3.15 beta 3. Use {@code OperandClassTransformer(FormulaType)} instead.
  58. */
  59. @Removal(version="3.17")
  60. public OperandClassTransformer(int formulaType) {
  61. this(FormulaType.forInt(formulaType));
  62. }
  63. public OperandClassTransformer(FormulaType formulaType) {
  64. _formulaType = formulaType;
  65. }
  66. /**
  67. * Traverses the supplied formula parse tree, calling <tt>Ptg.setClass()</tt> for each non-base
  68. * token to set its operand class.
  69. */
  70. public void transformFormula(ParseNode rootNode) {
  71. byte rootNodeOperandClass;
  72. switch (_formulaType) {
  73. case CELL:
  74. rootNodeOperandClass = Ptg.CLASS_VALUE;
  75. break;
  76. case ARRAY:
  77. rootNodeOperandClass = Ptg.CLASS_ARRAY;
  78. break;
  79. case NAMEDRANGE:
  80. case DATAVALIDATION_LIST:
  81. rootNodeOperandClass = Ptg.CLASS_REF;
  82. break;
  83. default:
  84. throw new RuntimeException("Incomplete code - formula type ("
  85. + _formulaType + ") not supported yet");
  86. }
  87. transformNode(rootNode, rootNodeOperandClass, false);
  88. }
  89. /**
  90. * @param callerForceArrayFlag <code>true</code> if one of the current node's parents is a
  91. * function Ptg which has been changed from default 'V' to 'A' type (due to requirements on
  92. * the function return value).
  93. */
  94. private void transformNode(ParseNode node, byte desiredOperandClass,
  95. boolean callerForceArrayFlag) {
  96. Ptg token = node.getToken();
  97. ParseNode[] children = node.getChildren();
  98. boolean isSimpleValueFunc = isSimpleValueFunction(token);
  99. if (isSimpleValueFunc) {
  100. boolean localForceArray = desiredOperandClass == Ptg.CLASS_ARRAY;
  101. for (int i = 0; i < children.length; i++) {
  102. transformNode(children[i], desiredOperandClass, localForceArray);
  103. }
  104. setSimpleValueFuncClass((AbstractFunctionPtg) token, desiredOperandClass, callerForceArrayFlag);
  105. return;
  106. }
  107. if (isSingleArgSum(token)) {
  108. // Need to process the argument of SUM with transformFunctionNode below
  109. // so make a dummy FuncVarPtg for that call.
  110. token = FuncVarPtg.SUM;
  111. // Note - the tAttrSum token (node.getToken()) is a base
  112. // token so does not need to have its operand class set
  113. }
  114. if (token instanceof ValueOperatorPtg || token instanceof ControlPtg
  115. || token instanceof MemFuncPtg
  116. || token instanceof MemAreaPtg
  117. || token instanceof UnionPtg
  118. || token instanceof IntersectionPtg) {
  119. // Value Operator Ptgs and Control are base tokens, so token will be unchanged
  120. // but any child nodes are processed according to desiredOperandClass and callerForceArrayFlag
  121. // As per OOO documentation Sec 3.2.4 "Token Class Transformation", "Step 1"
  122. // All direct operands of value operators that are initially 'R' type will
  123. // be converted to 'V' type.
  124. byte localDesiredOperandClass = desiredOperandClass == Ptg.CLASS_REF ? Ptg.CLASS_VALUE : desiredOperandClass;
  125. for (int i = 0; i < children.length; i++) {
  126. transformNode(children[i], localDesiredOperandClass, callerForceArrayFlag);
  127. }
  128. return;
  129. }
  130. if (token instanceof AbstractFunctionPtg) {
  131. transformFunctionNode((AbstractFunctionPtg) token, children, desiredOperandClass, callerForceArrayFlag);
  132. return;
  133. }
  134. if (children.length > 0) {
  135. if (token == RangePtg.instance) {
  136. // TODO is any token transformation required under the various ref operators?
  137. return;
  138. }
  139. throw new IllegalStateException("Node should not have any children");
  140. }
  141. if (token.isBaseToken()) {
  142. // nothing to do
  143. return;
  144. }
  145. token.setClass(transformClass(token.getPtgClass(), desiredOperandClass, callerForceArrayFlag));
  146. }
  147. private static boolean isSingleArgSum(Ptg token) {
  148. if (token instanceof AttrPtg) {
  149. AttrPtg attrPtg = (AttrPtg) token;
  150. return attrPtg.isSum();
  151. }
  152. return false;
  153. }
  154. private static boolean isSimpleValueFunction(Ptg token) {
  155. if (token instanceof AbstractFunctionPtg) {
  156. AbstractFunctionPtg aptg = (AbstractFunctionPtg) token;
  157. if (aptg.getDefaultOperandClass() != Ptg.CLASS_VALUE) {
  158. return false;
  159. }
  160. int numberOfOperands = aptg.getNumberOfOperands();
  161. for (int i=numberOfOperands-1; i>=0; i--) {
  162. if (aptg.getParameterClass(i) != Ptg.CLASS_VALUE) {
  163. return false;
  164. }
  165. }
  166. return true;
  167. }
  168. return false;
  169. }
  170. private byte transformClass(byte currentOperandClass, byte desiredOperandClass,
  171. boolean callerForceArrayFlag) {
  172. switch (desiredOperandClass) {
  173. case Ptg.CLASS_VALUE:
  174. if (!callerForceArrayFlag) {
  175. return Ptg.CLASS_VALUE;
  176. }
  177. // else fall through
  178. case Ptg.CLASS_ARRAY:
  179. return Ptg.CLASS_ARRAY;
  180. case Ptg.CLASS_REF:
  181. if (!callerForceArrayFlag) {
  182. return currentOperandClass;
  183. }
  184. return Ptg.CLASS_REF;
  185. }
  186. throw new IllegalStateException("Unexpected operand class (" + desiredOperandClass + ")");
  187. }
  188. private void transformFunctionNode(AbstractFunctionPtg afp, ParseNode[] children,
  189. byte desiredOperandClass, boolean callerForceArrayFlag) {
  190. boolean localForceArrayFlag;
  191. byte defaultReturnOperandClass = afp.getDefaultOperandClass();
  192. if (callerForceArrayFlag) {
  193. switch (defaultReturnOperandClass) {
  194. case Ptg.CLASS_REF:
  195. if (desiredOperandClass == Ptg.CLASS_REF) {
  196. afp.setClass(Ptg.CLASS_REF);
  197. } else {
  198. afp.setClass(Ptg.CLASS_ARRAY);
  199. }
  200. localForceArrayFlag = false;
  201. break;
  202. case Ptg.CLASS_ARRAY:
  203. afp.setClass(Ptg.CLASS_ARRAY);
  204. localForceArrayFlag = false;
  205. break;
  206. case Ptg.CLASS_VALUE:
  207. afp.setClass(Ptg.CLASS_ARRAY);
  208. localForceArrayFlag = true;
  209. break;
  210. default:
  211. throw new IllegalStateException("Unexpected operand class ("
  212. + defaultReturnOperandClass + ")");
  213. }
  214. } else {
  215. if (defaultReturnOperandClass == desiredOperandClass) {
  216. localForceArrayFlag = false;
  217. // an alternative would have been to for non-base Ptgs to set their operand class
  218. // from their default, but this would require the call in many subclasses because
  219. // the default OC is not known until the end of the constructor
  220. afp.setClass(defaultReturnOperandClass);
  221. } else {
  222. switch (desiredOperandClass) {
  223. case Ptg.CLASS_VALUE:
  224. // always OK to set functions to return 'value'
  225. afp.setClass(Ptg.CLASS_VALUE);
  226. localForceArrayFlag = false;
  227. break;
  228. case Ptg.CLASS_ARRAY:
  229. switch (defaultReturnOperandClass) {
  230. case Ptg.CLASS_REF:
  231. afp.setClass(Ptg.CLASS_REF);
  232. // afp.setClass(Ptg.CLASS_ARRAY);
  233. break;
  234. case Ptg.CLASS_VALUE:
  235. afp.setClass(Ptg.CLASS_ARRAY);
  236. break;
  237. default:
  238. throw new IllegalStateException("Unexpected operand class ("
  239. + defaultReturnOperandClass + ")");
  240. }
  241. localForceArrayFlag = (defaultReturnOperandClass == Ptg.CLASS_VALUE);
  242. break;
  243. case Ptg.CLASS_REF:
  244. switch (defaultReturnOperandClass) {
  245. case Ptg.CLASS_ARRAY:
  246. afp.setClass(Ptg.CLASS_ARRAY);
  247. break;
  248. case Ptg.CLASS_VALUE:
  249. afp.setClass(Ptg.CLASS_VALUE);
  250. break;
  251. default:
  252. throw new IllegalStateException("Unexpected operand class ("
  253. + defaultReturnOperandClass + ")");
  254. }
  255. localForceArrayFlag = false;
  256. break;
  257. default:
  258. throw new IllegalStateException("Unexpected operand class ("
  259. + desiredOperandClass + ")");
  260. }
  261. }
  262. }
  263. for (int i = 0; i < children.length; i++) {
  264. ParseNode child = children[i];
  265. byte paramOperandClass = afp.getParameterClass(i);
  266. transformNode(child, paramOperandClass, localForceArrayFlag);
  267. }
  268. }
  269. private void setSimpleValueFuncClass(AbstractFunctionPtg afp,
  270. byte desiredOperandClass, boolean callerForceArrayFlag) {
  271. if (callerForceArrayFlag || desiredOperandClass == Ptg.CLASS_ARRAY) {
  272. afp.setClass(Ptg.CLASS_ARRAY);
  273. } else {
  274. afp.setClass(Ptg.CLASS_VALUE);
  275. }
  276. }
  277. }