Index: LICENSE
===================================================================
--- LICENSE (revision 1832327)
+++ LICENSE (working copy)
@@ -284,3 +284,4 @@
* xpp3-1.1.4c.jar (Indiana University Extreme! Lab Software License 1.1.1)
* xstream-1.4.10.jar (BSD)
* hamcrest-date-2.0.4.jar (BSD)
+* saxon9he-9.8.0.11.jar (Mozilla Public License version 2.0)
\ No newline at end of file
Index: bin/saveservice.properties
===================================================================
--- bin/saveservice.properties (revision 1831500)
+++ bin/saveservice.properties (working copy)
@@ -364,7 +364,9 @@
XPathAssertionGui=org.apache.jmeter.assertions.gui.XPathAssertionGui
XPathExtractor=org.apache.jmeter.extractor.XPathExtractor
XPathExtractorGui=org.apache.jmeter.extractor.gui.XPathExtractorGui
-#
+XPath2Extractor=org.apache.jmeter.extractor.XPath2Extractor
+XPath2ExtractorGui=org.apache.jmeter.extractor.gui.XPath2ExtractorGui
+
# Properties - all start with lower case letter and end with Prop
#
boolProp=org.apache.jmeter.testelement.property.BooleanProperty
Index: build.properties
===================================================================
--- build.properties (revision 1832327)
+++ build.properties (working copy)
@@ -473,6 +473,12 @@
activemq-all.loc = ${maven2.repo}/org/apache/activemq/activemq-all/${activemq-all.version}
activemq-all.md5 = bd24ae082be11dc969a6e5bc45515ab7
+# Used by XPath 2
+saxon-HE.version = 9.8.0-12
+saxon-HE.jar = Saxon-HE-${saxon-HE.version}.jar
+saxon-HE.loc = ${maven2.repo}/net/sf/saxon/Saxon-HE/${saxon-HE.version}
+saxon-HE.md5 = 80e040add296c6545b92d7cb1b648534
+
# Optional for use by FTP_TESTS.jmx
mina-core.version = 2.0.16
mina-core.jar = mina-core-${mina-core.version}.jar
Index: build.xml
===================================================================
--- build.xml (revision 1831500)
+++ build.xml (working copy)
@@ -446,6 +446,7 @@
+
@@ -532,6 +533,7 @@
+
@@ -3400,6 +3402,7 @@
+
Index: eclipse.classpath
===================================================================
--- eclipse.classpath (revision 1832327)
+++ eclipse.classpath (working copy)
@@ -97,6 +97,7 @@
+
Index: lib/aareadme.txt
===================================================================
--- lib/aareadme.txt (revision 1832773)
+++ lib/aareadme.txt (working copy)
@@ -249,6 +249,10 @@
--------------------------
- DataSourceElement (JDBC)
+Saxon-HE-9.8.0-12 (net.sf.saxon)
+--------------------------
+- XPath2Extractor (XML)
+
velocity-1.7
--------------
http://velocity.apache.org/download.cgi
Index: licenses/bin/saxon-HE-9.8.0-12.txt
===================================================================
--- licenses/bin/saxon-HE-9.8.0-12.txt (revision 0)
+++ licenses/bin/saxon-HE-9.8.0-12.txt (revision 0)
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
Index: res/maven/ApacheJMeter_parent.pom
===================================================================
--- res/maven/ApacheJMeter_parent.pom (revision 1832327)
+++ res/maven/ApacheJMeter_parent.pom (working copy)
@@ -99,6 +99,7 @@
6.0.0
9.0.0
2.6.1
+ 9.8.0-12
1.7.25
1.7.25
2.10.0
@@ -333,6 +334,11 @@
${serializer.version}
+ net.sf.saxon
+ Saxon-HE
+ ${saxon-HE.version}
+
+
xerces
xercesImpl
${xerces.version}
Index: src/components/org/apache/jmeter/extractor/XPath2Extractor.java
===================================================================
--- src/components/org/apache/jmeter/extractor/XPath2Extractor.java (revision 0)
+++ src/components/org/apache/jmeter/extractor/XPath2Extractor.java (revision 0)
@@ -0,0 +1,359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.jmeter.extractor;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLStreamException;
+
+import org.apache.jmeter.assertions.AssertionResult;
+import org.apache.jmeter.processor.PostProcessor;
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jmeter.testelement.AbstractScopedTestElement;
+import org.apache.jmeter.testelement.property.BooleanProperty;
+import org.apache.jmeter.testelement.property.IntegerProperty;
+import org.apache.jmeter.threads.JMeterContext;
+import org.apache.jmeter.threads.JMeterVariables;
+import org.apache.jmeter.util.XPathUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.sf.saxon.s9api.SaxonApiException;
+
+/**
+ * Extracts text from (X)HTML response using XPath query language
+ * Example XPath queries:
+ *
+ * - /html/head/title
+ * - extracts Title from HTML response
+ * - //form[@name='countryForm']//select[@name='country']/option[text()='Czech Republic'])/@value
+ *
- extracts value attribute of option element that match text 'Czech Republic'
+ * inside of select element with name attribute 'country' inside of
+ * form with name attribute 'countryForm'
+ * - //head
+ * - extracts the XML fragment for head node.
+ * - //head/text()
+ * - extracts the text content for head node.
+ *
+ see org.apache.jmeter.extractor.TestXPathExtractor for unit tests
+ */
+public class XPath2Extractor extends AbstractScopedTestElement implements
+PostProcessor, Serializable {
+ private static final Logger log = LoggerFactory.getLogger(XPathExtractor.class);
+
+ private static final long serialVersionUID = 242L;
+
+ private static final int DEFAULT_VALUE = -1;
+ public static final String DEFAULT_VALUE_AS_STRING = Integer.toString(DEFAULT_VALUE);
+
+ private static final String REF_MATCH_NR = "matchNr"; // $NON-NLS-1$
+
+ //+ JMX file attributes
+ private static final String XPATH_QUERY = "XPathExtractor2.xpathQuery"; // $NON-NLS-1$
+ private static final String REFNAME = "XPathExtractor2.refname"; // $NON-NLS-1$
+ private static final String DEFAULT = "XPathExtractor2.default"; // $NON-NLS-1$
+ private static final String TOLERANT = "XPathExtractor2.tolerant"; // $NON-NLS-1$
+ private static final String QUIET = "XPathExtractor2.quiet"; // $NON-NLS-1$
+ private static final String REPORT_ERRORS = "XPathExtractor2.report_errors"; // $NON-NLS-1$
+ private static final String SHOW_WARNINGS = "XPathExtractor2.show_warnings"; // $NON-NLS-1$
+ private static final String DOWNLOAD_DTDS = "XPathExtractor2.download_dtds"; // $NON-NLS-1$
+ private static final String WHITESPACE = "XPathExtractor2.whitespace"; // $NON-NLS-1$
+ private static final String VALIDATE = "XPathExtractor2.validate"; // $NON-NLS-1$
+ private static final String FRAGMENT = "XPathExtractor2.fragment"; // $NON-NLS-1$
+ private static final String NAMESPACES = "XPathExtractor2.namespaces"; // $NON-NLS-1$
+ private static final String MATCH_NUMBER = "XPathExtractor2.matchNumber"; // $NON-NLS-1$
+ //- JMX file attributes
+
+ private String concat(String s1,String s2){
+ return new StringBuilder(s1).append("_").append(s2).toString(); // $NON-NLS-1$
+ }
+
+ private String concat(String s1, int i){
+ return new StringBuilder(s1).append("_").append(i).toString(); // $NON-NLS-1$
+ }
+
+
+ /**
+ * Do the job - extract value from (X)HTML response using XPath Query.
+ * Return value as variable defined by REFNAME. Returns DEFAULT value
+ * if not found.
+ */
+ @Override
+ public void process() {
+ JMeterContext context = getThreadContext();
+ final SampleResult previousResult = context.getPreviousResult();
+ if (previousResult == null){
+ return;
+ }
+ JMeterVariables vars = context.getVariables();
+ String refName = getRefName();
+ vars.put(refName, getDefaultValue());
+ final String matchNR = concat(refName,REF_MATCH_NR);
+ int prevCount=0; // number of previous matches
+ try {
+ prevCount=Integer.parseInt(vars.get(matchNR));
+ } catch (NumberFormatException e) {
+ // ignored
+ }
+
+ vars.put(matchNR, "0"); // In case parse fails // $NON-NLS-1$
+ vars.remove(concat(refName,"1")); // In case parse fails // $NON-NLS-1$
+
+ int matchNumber = getMatchNumber();
+ List matches = new ArrayList<>();
+ try{
+ if (isScopeVariable()){
+ String inputString=vars.get(getVariableName());
+ if(inputString != null) {
+ if(inputString.length()>0) {
+ getValuesForXPath(getXPathQuery(), matches, matchNumber,previousResult.getResponseDataAsString());
+ }
+ } else {
+ if (log.isWarnEnabled()) {
+ log.warn("No variable '{}' found to process by XPathExtractor '{}', skipping processing",
+ getVariableName(), getName());
+ }
+ }
+ } else {
+ List samples = getSampleList(previousResult);
+ int size = samples.size();
+ for(int i = 0;i 0){
+ String value = matches.get(0);
+ if (value != null) {
+ vars.put(refName, value);
+ }
+ for(int i=0; i < matchCount; i++){
+ value = matches.get(i);
+ if (value != null) {
+ vars.put(concat(refName,i+1),matches.get(i));
+ }
+ }
+ }
+ vars.remove(concat(refName,matchCount+1)); // Just in case
+ // Clear any other remaining variables
+ for(int i=matchCount+2; i <= prevCount; i++) {
+ vars.remove(concat(refName,i));
+ }
+ }catch (SaxonApiException e) {// Saxon exception
+ if (log.isWarnEnabled()) {
+ log.warn("SaxonApiException while processing ({}). {}", getXPathQuery(), e.getLocalizedMessage());
+ }
+ addAssertionFailure(previousResult, e, false);
+ } catch (XMLStreamException e) {
+ log.error("XMLStreamException on {}", e);
+ log.warn("XMLStreamException while processing {}", e.getLocalizedMessage());
+ } catch (FactoryConfigurationError e) {
+ log.error("FactoryConfigurationError on {}", e);
+ log.warn("FactoryConfigurationError while processing {}", e.getLocalizedMessage());
+
+ }
+ }
+
+ private void addAssertionFailure(final SampleResult previousResult,
+ final Throwable thrown, final boolean setFailed) {
+ AssertionResult ass = new AssertionResult(getName()); // $NON-NLS-1$
+ ass.setFailure(true);
+ ass.setFailureMessage(thrown.getLocalizedMessage()+"\nSee log file for further details.");
+ previousResult.addAssertionResult(ass);
+ if (setFailed){
+ previousResult.setSuccessful(false);
+ }
+ }
+
+ /*============= object properties ================*/
+ public void setXPathQuery(String val){
+ setProperty(XPATH_QUERY,val);
+ }
+
+ public String getXPathQuery(){
+ return getPropertyAsString(XPATH_QUERY);
+ }
+
+ public void setRefName(String refName) {
+ setProperty(REFNAME, refName);
+ }
+
+ public String getRefName() {
+ return getPropertyAsString(REFNAME);
+ }
+
+
+ public void setDefaultValue(String val) {
+ setProperty(DEFAULT, val);
+ }
+
+ public String getDefaultValue() {
+ return getPropertyAsString(DEFAULT);
+ }
+
+ public void setTolerant(boolean val) {
+ setProperty(new BooleanProperty(TOLERANT, val));
+ }
+
+ public boolean isTolerant() {
+ return getPropertyAsBoolean(TOLERANT);
+ }
+
+ public void setNameSpace(boolean val) {
+ setProperty(new BooleanProperty(NAMESPACES, val));
+ }
+
+ public boolean useNameSpace() {
+ return getPropertyAsBoolean(NAMESPACES);
+ }
+
+ public void setReportErrors(boolean val) {
+ setProperty(REPORT_ERRORS, val, false);
+ }
+
+ public boolean reportErrors() {
+ return getPropertyAsBoolean(REPORT_ERRORS, false);
+ }
+
+ public void setShowWarnings(boolean val) {
+ setProperty(SHOW_WARNINGS, val, false);
+ }
+
+ public boolean showWarnings() {
+ return getPropertyAsBoolean(SHOW_WARNINGS, false);
+ }
+
+ public void setQuiet(boolean val) {
+ setProperty(QUIET, val, true);
+ }
+
+ public boolean isQuiet() {
+ return getPropertyAsBoolean(QUIET, true);
+ }
+
+ /**
+ * Should we return fragment as text, rather than text of fragment?
+ * @return true if we should return fragment rather than text
+ */
+ public boolean getFragment() {
+ return getPropertyAsBoolean(FRAGMENT, false);
+ }
+
+ /**
+ * Should we return fragment as text, rather than text of fragment?
+ * @param selected true to return fragment.
+ */
+ public void setFragment(boolean selected) {
+ setProperty(FRAGMENT, selected, false);
+ }
+
+ /*================= internal business =================*/
+
+ /**
+ * Extract value from String responseData by XPath query.
+ * @param query the query to execute
+ * @param matchStrings list of matched strings (may include nulls)
+ * @param matchNumber int Match Number
+ * @param responseData String that contains the entire Document
+ * @throws SaxonApiException
+ * @throws XMLStreamException
+ * @throws FactoryConfigurationError
+ */
+ private void getValuesForXPath(String query, List matchStrings, int matchNumber, String responseData)
+ throws SaxonApiException, XMLStreamException, FactoryConfigurationError {
+ XPathUtil.putValuesForXPathInListUsingSaxon(responseData, query, matchStrings, getFragment(), matchNumber, getNamespaces());
+ }
+
+
+
+ public void setWhitespace(boolean selected) {
+ setProperty(WHITESPACE, selected, false);
+ }
+
+ public boolean isWhitespace() {
+ return getPropertyAsBoolean(WHITESPACE, false);
+ }
+
+ public void setValidating(boolean selected) {
+ setProperty(VALIDATE, selected);
+ }
+
+ public boolean isValidating() {
+ return getPropertyAsBoolean(VALIDATE, false);
+ }
+
+ public void setDownloadDTDs(boolean selected) {
+ setProperty(DOWNLOAD_DTDS, selected, false);
+ }
+
+ public boolean isDownloadDTDs() {
+ return getPropertyAsBoolean(DOWNLOAD_DTDS, false);
+ }
+
+ /**
+ * Set which Match to use. This can be any positive number, indicating the
+ * exact match to use, or 0
, which is interpreted as meaning random.
+ *
+ * @param matchNumber The number of the match to be used
+ */
+ public void setMatchNumber(int matchNumber) {
+ setProperty(new IntegerProperty(MATCH_NUMBER, matchNumber));
+ }
+
+ /**
+ * Set which Match to use. This can be any positive number, indicating the
+ * exact match to use, or 0
, which is interpreted as meaning random.
+ *
+ * @param matchNumber The number of the match to be used
+ */
+ public void setMatchNumber(String matchNumber) {
+ setProperty(MATCH_NUMBER, matchNumber);
+ }
+
+ /**
+ * Return which Match to use. This can be any positive number, indicating the
+ * exact match to use, or 0
, which is interpreted as meaning random.
+ *
+ * @return matchNumber The number of the match to be used
+ */
+ public int getMatchNumber() {
+ return getPropertyAsInt(MATCH_NUMBER, DEFAULT_VALUE);
+ }
+
+ /**
+ * Return which Match to use. This can be any positive number, indicating the
+ * exact match to use, or 0
, which is interpreted as meaning random.
+ *
+ * @return matchNumber The number of the match to be used
+ */
+ public String getMatchNumberAsString() {
+ return getPropertyAsString(MATCH_NUMBER, DEFAULT_VALUE_AS_STRING);
+ }
+
+ public void setNamespaces(String namespaces) {
+ setProperty(NAMESPACES, namespaces);
+ }
+
+ public String getNamespaces() {
+ return getPropertyAsString(NAMESPACES);
+ }
+}
Index: src/components/org/apache/jmeter/extractor/gui/XPath2ExtractorGui.java
===================================================================
--- src/components/org/apache/jmeter/extractor/gui/XPath2ExtractorGui.java (revision 0)
+++ src/components/org/apache/jmeter/extractor/gui/XPath2ExtractorGui.java (revision 0)
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.jmeter.extractor.gui;
+
+import java.awt.BorderLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.util.List;
+
+import javax.swing.Box;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import org.apache.jmeter.extractor.XPath2Extractor;
+import org.apache.jmeter.gui.util.JSyntaxTextArea;
+import org.apache.jmeter.gui.util.JTextScrollPane;
+import org.apache.jmeter.processor.gui.AbstractPostProcessorGui;
+import org.apache.jmeter.testelement.TestElement;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jorphan.gui.JLabeledTextField;
+
+/**
+ * GUI for XPath2Extractor class.
+ * @since 4.1
+ */
+public class XPath2ExtractorGui extends AbstractPostProcessorGui{ // NOSONAR Ignore parents warning
+
+ private static final long serialVersionUID = 1L;
+
+ private final JLabeledTextField defaultField = new JLabeledTextField(
+ JMeterUtils.getResString("default_value_field"));//$NON-NLS-1$
+
+ private final JLabeledTextField xpathQueryField = new JLabeledTextField(
+ JMeterUtils.getResString("xpath_extractor_query"));//$NON-NLS-1$
+
+ private final JLabeledTextField matchNumberField = new JLabeledTextField(
+ JMeterUtils.getResString("match_num_field"));//$NON-NLS-1$
+
+ private final JLabeledTextField refNameField = new JLabeledTextField(JMeterUtils.getResString("ref_name_field"));//$NON-NLS-1$
+
+ // Should we return fragment as text, rather than text of fragment?
+ private JCheckBox getFragment;
+
+ private JSyntaxTextArea namespacesTA;
+
+ @Override
+ public String getLabelResource() {
+ return "xpath2_extractor_title"; //$NON-NLS-1$
+ }
+
+ public XPath2ExtractorGui() {
+ super();
+ init();
+ }
+
+ @Override
+ public void configure(TestElement el) {
+ super.configure(el);
+ XPath2Extractor xpe = (XPath2Extractor) el;
+ showScopeSettings(xpe, true);
+ xpathQueryField.setText(xpe.getXPathQuery());
+ defaultField.setText(xpe.getDefaultValue());
+ refNameField.setText(xpe.getRefName());
+ matchNumberField.setText(xpe.getMatchNumberAsString());
+ namespacesTA.setText(xpe.getNamespaces());
+ getFragment.setSelected(xpe.getFragment());
+ }
+
+ @Override
+ public TestElement createTestElement() {
+ XPath2Extractor extractor = new XPath2Extractor();
+ modifyTestElement(extractor);
+ return extractor;
+ }
+
+ @Override
+ public void modifyTestElement(TestElement extractor) {
+ super.configureTestElement(extractor);
+ if (extractor instanceof XPath2Extractor) {
+ XPath2Extractor xpath = (XPath2Extractor) extractor;
+ saveScopeSettings(xpath);
+ xpath.setDefaultValue(defaultField.getText());
+ xpath.setRefName(refNameField.getText());
+ xpath.setMatchNumber(matchNumberField.getText());
+ xpath.setXPathQuery(xpathQueryField.getText());
+ xpath.setFragment(getFragment.isSelected());
+ xpath.setNamespaces(namespacesTA.getText());
+ }
+ }
+
+ /**
+ * Implements JMeterGUIComponent.clearGui
+ */
+ @Override
+ public void clearGui() {
+ super.clearGui();
+ xpathQueryField.setText(""); // $NON-NLS-1$
+ defaultField.setText(""); // $NON-NLS-1$
+ refNameField.setText(""); // $NON-NLS-1$
+ matchNumberField.setText(XPath2Extractor.DEFAULT_VALUE_AS_STRING); // $NON-NLS-1$
+ namespacesTA.setText("");
+ }
+
+ private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
+ setLayout(new BorderLayout());
+ setBorder(makeBorder());
+ Box box = Box.createVerticalBox();
+ box.add(makeTitlePanel());
+ box.add(createScopePanel(true, true, true));
+ box.add(makeParameterPanel());
+ add(box, BorderLayout.NORTH);
+ }
+
+ private JPanel makeParameterPanel() {
+ JPanel panel = new JPanel(new GridBagLayout());
+ GridBagConstraints gbc = new GridBagConstraints();
+ initConstraints(gbc);
+ addField(panel, refNameField, gbc);
+ resetContraints(gbc);
+ addField(panel, xpathQueryField, gbc);
+ resetContraints(gbc);
+ addField(panel, matchNumberField, gbc);
+ resetContraints(gbc);
+ addField(panel, defaultField, gbc);
+ resetContraints(gbc);
+ panel.add(new JLabel(JMeterUtils.getResString("xpath_extractor_user_namespaces")), gbc.clone());
+ gbc.gridx++;
+ gbc.weightx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ namespacesTA = JSyntaxTextArea.getInstance(5, 80);
+ panel.add(JTextScrollPane.getInstance(namespacesTA, true), gbc.clone());
+
+ resetContraints(gbc);
+ gbc.gridwidth = 2;
+ getFragment = new JCheckBox(JMeterUtils.getResString("xpath_extractor_fragment"));//$NON-NLS-1$
+ panel.add(getFragment, gbc.clone());
+ return panel;
+ }
+
+ private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) {
+ List item = field.getComponentList();
+ panel.add(item.get(0), gbc.clone());
+ gbc.gridx++;
+ gbc.weightx = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ panel.add(item.get(1), gbc.clone());
+ }
+
+ private void resetContraints(GridBagConstraints gbc) {
+ gbc.gridx = 0;
+ gbc.gridy++;
+ gbc.weightx = 0;
+ gbc.fill = GridBagConstraints.NONE;
+ }
+
+ private void initConstraints(GridBagConstraints gbc) {
+ gbc.anchor = GridBagConstraints.NORTHWEST;
+ gbc.fill = GridBagConstraints.NONE;
+ gbc.gridheight = 1;
+ gbc.gridwidth = 1;
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.weightx = 0;
+ gbc.weighty = 0;
+ }
+}
Index: src/components/org/apache/jmeter/visualizers/RenderAsXPath2.java
===================================================================
--- src/components/org/apache/jmeter/visualizers/RenderAsXPath2.java (revision 0)
+++ src/components/org/apache/jmeter/visualizers/RenderAsXPath2.java (revision 0)
@@ -0,0 +1,293 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jmeter.visualizers;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.jmeter.extractor.XPath2Extractor;
+import org.apache.jmeter.gui.util.JSyntaxTextArea;
+import org.apache.jmeter.gui.util.JTextScrollPane;
+import org.apache.jmeter.samplers.SampleResult;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jmeter.util.XPathUtil;
+import org.apache.jorphan.gui.GuiUtils;
+import org.apache.jorphan.gui.JLabeledTextField;
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Implement ResultsRender for XPath tester
+ */
+public class RenderAsXPath2 implements ResultRenderer, ActionListener {
+
+ private static final Logger log = LoggerFactory.getLogger(RenderAsXPath.class);
+
+ private static final String XPATH_TESTER_COMMAND = "xpath_tester"; // $NON-NLS-1$
+
+ private static final String XPATH_NAMESPACES_COMMAND = "xpath_namespaces"; // $NON-NLS-1$
+
+ private JPanel xmlWithXPathPane;
+
+ private JSyntaxTextArea xmlDataField;
+
+ private JLabeledTextField xpathExpressionField;
+
+ private JTextArea xpathResultField;
+
+ private JTabbedPane rightSide;
+
+ private SampleResult sampleResult = null;
+
+ // Should we return fragment as text, rather than text of fragment?
+ private final JCheckBox getFragment =
+ new JCheckBox(JMeterUtils.getResString("xpath_tester_fragment"));//$NON-NLS-1$
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearData() {
+ // N.B. don't set xpathExpressionField to empty to keep xpath
+ this.xmlDataField.setText(""); // $NON-NLS-1$
+ this.xpathResultField.setText(""); // $NON-NLS-1$
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void init() {
+ // Create the panels for the xpath tab
+ xmlWithXPathPane = createXpathExtractorPanel();
+ }
+
+ /**
+ * Display the response as text or as rendered HTML. Change the text on the
+ * button appropriate to the current display.
+ *
+ * @param e the ActionEvent being processed
+ */
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String command = e.getActionCommand();
+ if ((sampleResult != null) && (XPATH_TESTER_COMMAND.equals(command))) {
+ String response = xmlDataField.getText();
+ XPath2Extractor extractor = new XPath2Extractor();
+ extractor.setFragment(getFragment.isSelected());
+ executeAndShowXPathTester(response, extractor);
+ }
+ else if ((sampleResult != null) && (XPATH_NAMESPACES_COMMAND.equals(command))) {
+ String response = xmlDataField.getText();
+ this.xpathResultField.setText(getDocumentNamespaces(response));
+ }
+ }
+
+ /**
+ * Launch xpath engine to parse a input text
+ * @param textToParse
+ */
+ private void executeAndShowXPathTester(String textToParse, XPath2Extractor extractor) {
+ if (textToParse != null && textToParse.length() > 0
+ && this.xpathExpressionField.getText().length() > 0) {
+ this.xpathResultField.setText(process(textToParse, extractor));
+ this.xpathResultField.setCaretPosition(0); // go to first line
+ }
+ }
+
+ private String process(String textToParse, XPath2Extractor extractor) {
+ try {
+ List matchStrings = new ArrayList<>();
+ XPathUtil.putValuesForXPathInListUsingSaxon(textToParse, xpathExpressionField.getText(), matchStrings, extractor.getFragment(), -1, getDocumentNamespaces(textToParse));
+ StringBuilder builder = new StringBuilder();
+ int nbFound = matchStrings.size();
+ builder.append("Match count: ").append(nbFound).append("\n");
+ for (int i = 0; i < nbFound; i++) {
+ builder.append("Match[").append(i+1).append("]=").append(matchStrings.get(i)).append("\n");
+ }
+ return builder.toString();
+ } catch (Exception e) {
+ return "Exception:"+ ExceptionUtils.getStackTrace(e);
+ }
+ }
+
+
+ private String getDocumentNamespaces(String textToParse) {
+ StringBuilder result = new StringBuilder();
+ try {
+ List namespaces = XPathUtil.getNamespaces(textToParse);
+ for (int i = 0;i, XPathExecutable> {
+
+ private static final Logger log = LoggerFactory.getLogger(XPathQueryCacheLoader.class);
+
+ @Override
+ public XPathExecutable load(ImmutablePair key)
+ throws Exception {
+ String xPathQuery = key.left;
+ String namespacesString = key.right;
+
+ Processor processor = XPathUtil.getProcessor();
+ XPathCompiler xPathCompiler = processor.newXPathCompiler();
+
+ List namespacesList = XPathUtil.namespacesParse(namespacesString);
+ log.debug("Parsed namespaces:{} into list of namespaces:{}", namespacesString, namespacesList);
+ for (String[] namespaces : namespacesList) {
+ xPathCompiler.declareNamespace(namespaces[0], namespaces[1]);
+ }
+ log.debug("Declared namespaces:{}, now compiling xPathQuery:{}", namespacesList, xPathQuery);
+ return xPathCompiler.compile(xPathQuery);
+ }
+}
Index: src/core/org/apache/jmeter/util/XPathUtil.java
===================================================================
--- src/core/org/apache/jmeter/util/XPathUtil.java (revision 1831500)
+++ src/core/org/apache/jmeter/util/XPathUtil.java (working copy)
@@ -26,11 +26,17 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
@@ -39,7 +45,9 @@
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.xml.utils.PrefixResolver;
import org.apache.xpath.XPathAPI;
@@ -57,16 +65,44 @@
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.LoadingCache;
+
+import net.sf.saxon.s9api.Processor;
+import net.sf.saxon.s9api.SaxonApiException;
+import net.sf.saxon.s9api.XPathExecutable;
+import net.sf.saxon.s9api.XPathSelector;
+import net.sf.saxon.s9api.XdmItem;
+import net.sf.saxon.s9api.XdmNode;
+import net.sf.saxon.s9api.XdmValue;
+
/**
* This class provides a few utility methods for dealing with XML/XPath.
*/
public class XPathUtil {
+
private static final Logger log = LoggerFactory.getLogger(XPathUtil.class);
+ private static final LoadingCache, XPathExecutable> XPATH_CACHE;
+ static {
+ final int cacheSize = JMeterUtils.getPropDefault(
+ "xpath2query.parser.cache.size", 400);
+ XPATH_CACHE = Caffeine.newBuilder().maximumSize(cacheSize).build(new XPathQueryCacheLoader());
+ }
+
+ /**
+ *
+ */
+ private static final Processor PROCESSOR = new Processor(false);
+
private XPathUtil() {
super();
}
+ public static Processor getProcessor() {
+ return PROCESSOR;
+ }
+
private static DocumentBuilderFactory documentBuilderFactory;
/**
@@ -289,6 +325,7 @@
return sw.toString();
}
+
/**
* @param node {@link Node}
* @return String content of node
@@ -330,6 +367,7 @@
}
+
/**
* Put in matchStrings results of evaluation
* @param document XML document
@@ -339,10 +377,8 @@
* @param matchNumber match number
* @throws TransformerException when the internally used xpath engine fails
*/
- public static void putValuesForXPathInList(Document document,
- String xPathQuery,
- List matchStrings, boolean fragment,
- int matchNumber) throws TransformerException {
+ public static void putValuesForXPathInList(Document document, String xPathQuery, List matchStrings, boolean fragment, int matchNumber)
+ throws TransformerException {
String val = null;
XObject xObject = XPathAPI.eval(document, xPathQuery, getPrefixResolver(document));
final int objectType = xObject.getType();
@@ -381,6 +417,144 @@
}
}
+ public static void putValuesForXPathInListUsingSaxon(
+ String xmlFile, String xPathQuery,
+ List matchStrings, boolean fragment,
+ int matchNumber, String namespaces)
+ throws SaxonApiException, FactoryConfigurationError {
+
+ // generating the cache key
+ final ImmutablePair key = ImmutablePair.of(xPathQuery, namespaces);
+
+ //check the cache
+ XPathExecutable xPathExecutable;
+ if(!xPathQuery.equals("")) {
+ xPathExecutable = XPATH_CACHE.get(key);
+ }
+ else {
+ log.warn("Error : {}", JMeterUtils.getResString("xpath2_extractor_empty_query"));
+ return;
+ }
+
+ try (StringReader reader = new StringReader(xmlFile)) {
+ // We could instanciate it once but might trigger issues in the future
+ // Sharing of a DocumentBuilder across multiple threads is not recommended.
+ // However, in the current implementation sharing a DocumentBuilder (once initialized)
+ // will only cause problems if a SchemaValidator is used.
+ net.sf.saxon.s9api.DocumentBuilder builder = PROCESSOR.newDocumentBuilder();
+ XdmNode xdmNode = builder.build(new StreamSource(reader));
+
+ if(xPathExecutable!=null) {
+ XPathSelector selector = xPathExecutable.load();
+ selector.setContextItem(xdmNode);
+ XdmValue nodes = selector.evaluate();
+ int length = nodes.size();
+ int indexToMatch = matchNumber;
+ // In case we need to extract everything
+ if(matchNumber < 0) {
+ for(XdmItem item : nodes) {
+ if(fragment) {
+ matchStrings.add(item.toString());
+ }
+ else {
+ matchStrings.add(item.getStringValue());
+ }
+ }
+ } else {
+ if(indexToMatch <= length) {
+ if(matchNumber == 0 && length>0) {
+ indexToMatch = JMeterUtils.getRandomInt(length)+1;
+ }
+ XdmItem item = nodes.itemAt(indexToMatch-1);
+ matchStrings.add(fragment ? item.toString() : item.getStringValue());
+ } else {
+ if(log.isWarnEnabled()) {
+ log.warn("Error : {}{}", JMeterUtils.getResString("xpath2_extractor_match_number_failure"),indexToMatch);
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ public static List namespacesParse(String namespaces) {
+ List res = new ArrayList<>();
+ int length = namespaces.length();
+ int startWord = 0;
+ boolean afterEqual = false;
+ int positionLastKey = -1;
+ for(int i = 0; i < length; i++) {
+ char actualChar = namespaces.charAt(i);
+ if(actualChar=='=' && !afterEqual) {
+ String [] tmp = new String[2];
+ tmp[0] = namespaces.substring(startWord, i);
+ res.add(tmp);
+ afterEqual = true;
+ startWord = i+1;
+ positionLastKey++;
+ }
+ else if(namespaces.charAt(i)=='\n' && afterEqual) {
+ afterEqual = false;
+ res.get(positionLastKey)[1]= namespaces.substring(startWord, i);
+ startWord = i+1;
+ }
+ else if(namespaces.charAt(i)=='\n') {
+ startWord = i+1;
+ }
+ else if(i==length-1 && afterEqual) {
+ res.get(positionLastKey)[1]= namespaces.substring(startWord, i+1);
+ }
+ }
+ return res;
+ }
+
+ public static List getNamespaces(String xml)
+ throws XMLStreamException, FactoryConfigurationError{
+ List res= new ArrayList<>();
+ XMLStreamReader reader;
+ if(!xml.equals("")) {
+ reader = XMLInputFactory.newFactory().createXMLStreamReader(new StringReader(xml));
+ while (reader.hasNext()) {
+ int event = reader.next();
+ if (XMLStreamConstants.START_ELEMENT == event) {
+ addToList(reader, res);
+ } else if(XMLStreamConstants.NAMESPACE == event) {
+ // This almost never happens
+ addToList(reader, res);
+ }
+ }
+ }
+ return res;
+ }
+
+ private static void addToList(XMLStreamReader reader, List res) {
+ boolean isInList = false;
+ int namespaceCount = reader.getNamespaceCount();
+ if (namespaceCount > 0) {
+ for (int nsIndex = 0; nsIndex < namespaceCount; nsIndex++) {
+ String[] tmp = {"",""};
+ String nsPrefix = reader.getNamespacePrefix(nsIndex);
+ for(int i = 0;i
+
+
+ 29
+ Pankaj
+ Male
+ Java Developer
+
+
+ 35
+ Lisa
+ Female
+ CEO
+
+
+ 45
+ Tom
+ Male
+ Manager
+
+
+ 55
+ Meghan
+ Female
+ Manager
+
+
\ No newline at end of file
Index: test/src/org/apache/jmeter/util/XPathUtilTest.java
===================================================================
--- test/src/org/apache/jmeter/util/XPathUtilTest.java (revision 1831500)
+++ test/src/org/apache/jmeter/util/XPathUtilTest.java (working copy)
@@ -18,17 +18,110 @@
package org.apache.jmeter.util;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.stream.FactoryConfigurationError;
import org.hamcrest.CoreMatchers;
import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public class XPathUtilTest {
+import net.sf.saxon.s9api.SaxonApiException;
+public class XPathUtilTest {
+ private static final Logger log = LoggerFactory.getLogger(XPathUtil.class);
final String lineSeparator = System.getProperty("line.separator");
+ final String xmlDoc = JMeterUtils.getResourceFileAsText("XPathUtilTestXml.xml");
+
+ @Test
+ public void testputValuesForXPathInListUsingSaxon(){
+
+ String xPathQuery="//Employees/Employee/role";
+ ArrayList matchStrings = new ArrayList();
+ boolean fragment = false;
+ String namespaces = "age=http://www.w3.org/2003/01/geo/wgs84_pos#";
+ int matchNumber = 3;
+
+ try {
+ XPathUtil.putValuesForXPathInListUsingSaxon(xmlDoc, xPathQuery, matchStrings, fragment, matchNumber, namespaces);
+ assertEquals("Manager",matchStrings.get(0));
+
+ matchNumber = 0;
+ xPathQuery="//Employees/Employee[1]/age:ag";
+ fragment = true;
+ matchStrings.clear();
+ XPathUtil.putValuesForXPathInListUsingSaxon(xmlDoc, xPathQuery, matchStrings, fragment, matchNumber, namespaces);
+ assertEquals("29",matchStrings.get(0));
+ assertEquals(1,matchStrings.size());
+
+ matchNumber = -1;
+ xPathQuery="//Employees/Employee/age:ag";
+ matchStrings.clear();
+ XPathUtil.putValuesForXPathInListUsingSaxon(xmlDoc, xPathQuery, matchStrings, fragment, matchNumber, namespaces);
+ assertEquals("29",matchStrings.get(0));
+ assertEquals(4,matchStrings.size());
+
+ fragment = false;
+ matchStrings.clear();
+ XPathUtil.putValuesForXPathInListUsingSaxon(xmlDoc, xPathQuery, matchStrings, fragment, matchNumber, namespaces);
+ assertEquals("29",matchStrings.get(0));
+ assertEquals(4,matchStrings.size());
+
+ matchStrings.clear();
+ xPathQuery="regtsgwsdfstgsdf";
+ XPathUtil.putValuesForXPathInListUsingSaxon(xmlDoc, xPathQuery, matchStrings, fragment, matchNumber, namespaces);
+ assertEquals(new ArrayList(),matchStrings);
+ assertEquals(0,matchStrings.size());
+
+ matchStrings.clear();
+ xPathQuery="//Employees/Employee[1]/age:ag";
+ matchNumber = 555;
+ XPathUtil.putValuesForXPathInListUsingSaxon(xmlDoc, xPathQuery, matchStrings, fragment, matchNumber, namespaces);
+ assertEquals(new ArrayList(),matchStrings);
+ assertEquals(0,matchStrings.size());
+
+ } catch (SaxonApiException e) {
+ if (log.isWarnEnabled()) {
+ log.warn("SaxonApiException while processing ({}). {}", xPathQuery, e.getLocalizedMessage());
+ }
+ }catch(FactoryConfigurationError e) {
+ log.error("FactoryConfigurationError on {}", e);
+ log.warn("FactoryConfigurationError while processing {}", e.getLocalizedMessage());
+ }
+ }
+
+ @Test
+ public void testnamespacesParse() {
+ String namespaces = "donald=duck";
+ List test = XPathUtil.namespacesParse(namespaces);
+ assertEquals("donald",test.get(0)[0]);
+ assertEquals("duck",test.get(0)[1]);
+
+ namespaces = "donald=duck\nmickey=mouse";
+ test = XPathUtil.namespacesParse(namespaces);
+ assertEquals("donald",test.get(0)[0]);
+ assertEquals("duck",test.get(0)[1]);
+ assertEquals("mickey",test.get(1)[0]);
+ assertEquals("mouse",test.get(1)[1]);
+
+ namespaces = "donald=duck\n\n\nmickey=mouse";
+ test = XPathUtil.namespacesParse(namespaces);
+ assertEquals("mickey",test.get(1)[0]);
+ assertEquals("mouse",test.get(1)[1]);
+
+ namespaces = "geo=patate\n \n \n\nmickey=mouse\n\n \n";
+ test = XPathUtil.namespacesParse(namespaces);
+ assertEquals("mickey",test.get(1)[0]);
+ assertEquals("mouse",test.get(1)[1]);
+ }
+
@Test
public void testFormatXmlSimple() {
assertThat(XPathUtil.formatXml("Test"),
@@ -49,6 +142,7 @@
"")));
}
+
@Test()
public void testFormatXmlInvalid() {
PrintStream origErr = System.err;
Index: xdocs/usermanual/component_reference.xml
===================================================================
--- xdocs/usermanual/component_reference.xml (revision 1832327)
+++ xdocs/usermanual/component_reference.xml (working copy)
@@ -5758,6 +5758,58 @@
+
+ This test element allows the user to extract value(s) from
+ structured response - XML or (X)HTML - using XPath2
+ query language.
+
+
+ Descriptive name for this element that is shown in the tree.
+
+ This is for use with samplers that can generate sub-samples,
+ e.g. HTTP Sampler with embedded resources, Mail Reader or samples generated by the Transaction Controller.
+
+ Main sample only
- only applies to the main sample
+ Sub-samples only
- only applies to the sub-samples
+ Main sample and sub-samples
- applies to both.
+ JMeter Variable Name to use
- extraction is to be applied to the contents of the named variable
+
+ XPath matching is applied to all qualifying samples in turn, and all the matching results will be returned.
+
+
+ If selected, the fragment will be returned rather than the text content.
+ For example //title
would return "<title>Apache JMeter</title>
" rather than "Apache JMeter
".
+ In this case, //title/text()
would return "Apache JMeter
".
+
+ The name of the JMeter variable in which to store the result.
+ Element query in XPath language. Can return more than one match.
+ If the XPath Path query leads to many results, you can choose which one(s) to extract as Variables:
+
+ 0
: means random
+ -1
means extract all results (default value), they will be named as <variable name>_N
(where N
goes from 1 to Number of results)
+ X
: means extract the Xth result. If this Xth is greater than number of matches, then nothing is returned. Default value will be used
+
+
+ Default value returned when no match found.
+ It is also returned if the node has no value and the fragment option is not selected.
+ List of namespaces aliases you want to use to parse the document.
+ You must specify them as follow : prefix=namespace
. This implementation makes it easier to
+ use namespaces than in the old XPathExtractor version.
+
+ To allow for use in a , it works exactly the same as the above XPath Extractor
+ XPath2 Extractor provides some interestings tools such as an improved syntax and much more functions than in it's first version.
+ Here are some exemples :
+
+ abs(/book/page[2])
+ - extracts 2nd absolute value of the page from a book
+ avg(/librarie/book/page)
+ - extracts the average number of page from all the books in the libraries
+ compare(/book[1]/page[2],/book[2]/page[2])
+ - return Integer value equal 0 to if the 2nd page of the first book is equal to the 2nd page of the 2nd book, else return -1.
+
+ To see more informations about thoses fuctions, please check xPath2 functions
+
+
This test element allows the user to extract value(s) from
structured response - XML or (X)HTML - using XPath