aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordanielhanrahan <daniel.hanrahan@est.tech>2023-06-28 12:55:20 +0100
committerdanielhanrahan <daniel.hanrahan@est.tech>2023-07-20 10:08:50 +0100
commit74a47154f3bce495d9f58a300a860d750ae309f1 (patch)
tree5567ea2e3e53c1867f5db94a49edc31505ea658d
parent6a2eca2859d8b2ab88ff04663902eb7cc74b4fc1 (diff)
Apostrophe handling in CpsPathParser
Apostrophe is not currently handled correctly, and having apostrophe in the xpath will lead to various errors. For example, normalizing this xpath works: /path[@name="I'm quoted"] -> /path[@name='I\'m quoted'] However the resulting xpath will throw a PathParsingException if parsed! (Thus path normalization is not idempotent.) - Use '' for escaping apostrophe in single quoted leaf value, to comply with XPath standard (and use "" for escaping in "). - Use Liquibase to make existing data comply with new rules. - Leaf values in data leaves are now unescaped, e.g. "I'm quoted" - Quoting is now consistent for leaf/text/contains conditions. Issue-ID: CPS-1769 Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech> Change-Id: Iafc287f738254d7f99706c6bc548091c0ecd5aa0
-rw-r--r--cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g414
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java37
-rw-r--r--cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy8
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java2
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java7
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java8
-rw-r--r--cps-ri/src/main/resources/changelog/changelog-master.yaml2
-rw-r--r--cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql19
-rw-r--r--cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql19
-rw-r--r--cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml29
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy7
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangUtils.java2
-rw-r--r--docs/cps-path.rst3
-rwxr-xr-xdocs/release-notes.rst1
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy19
15 files changed, 143 insertions, 34 deletions
diff --git a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4 b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
index c88a82265..3aef120fe 100644
--- a/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
+++ b/cps-path-parser/src/main/antlr4/org/onap/cps/cpspath/parser/antlr4/CpsPath.g4
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2022 Nordix Foundation
+ * Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +19,12 @@
* ============LICENSE_END=========================================================
*/
+/*
+ * Parser Rules
+ * Some of the parser rules below are inspired by
+ * https://github.com/antlr/grammars-v4/blob/master/xpath/xpath31/XPath31Parser.g4
+ */
+
grammar CpsPath ;
cpsPath : ( prefix | descendant | incorrectPrefix ) multipleLeafConditions? textFunctionCondition? containsFunctionCondition? ancestorAxis? invalidPostFix?;
@@ -60,7 +66,7 @@ invalidPostFix : (AT | CB | COLONCOLON | comparativeOperators ).+ ;
/*
* Lexer Rules
* Most of the lexer rules below are inspired by
- * https://raw.githubusercontent.com/antlr/grammars-v4/master/xpath/xpath31/XPath31.g4
+ * https://github.com/antlr/grammars-v4/blob/master/xpath/xpath31/XPath31Lexer.g4
*/
AT : '@' ;
@@ -89,9 +95,9 @@ IntegerLiteral : FragDigits ;
// Add below type definitions for leafvalue comparision in https://jira.onap.org/browse/CPS-440
DecimalLiteral : ('.' FragDigits) | (FragDigits '.' [0-9]*) ;
DoubleLiteral : (('.' FragDigits) | (FragDigits ('.' [0-9]*)?)) [eE] [+-]? FragDigits ;
-StringLiteral : ('"' (FragEscapeQuot | ~[^"])*? '"') | ('\'' (FragEscapeApos | ~['])*? '\'') ;
+StringLiteral : '"' (~["] | FragEscapeQuot)* '"' | '\'' (~['] | FragEscapeApos)* '\'' ;
fragment FragEscapeQuot : '""' ;
-fragment FragEscapeApos : '\'' ;
+fragment FragEscapeApos : '\'\'';
fragment FragDigits : [0-9]+ ;
QName : FragQName ;
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
index 99135962f..de261e64b 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java
@@ -79,18 +79,13 @@ public class CpsPathBuilder extends CpsPathBaseListener {
@Override
public void exitLeafCondition(final LeafConditionContext ctx) {
- Object comparisonValue;
+ final Object comparisonValue;
if (ctx.IntegerLiteral() != null) {
comparisonValue = Integer.valueOf(ctx.IntegerLiteral().getText());
} else if (ctx.StringLiteral() != null) {
- final boolean wasWrappedInDoubleQuote = ctx.StringLiteral().getText().startsWith("\"");
- comparisonValue = stripFirstAndLastCharacter(ctx.StringLiteral().getText());
- if (wasWrappedInDoubleQuote) {
- comparisonValue = String.valueOf(comparisonValue).replace("'", "\\'");
- }
+ comparisonValue = unwrapQuotedString(ctx.StringLiteral().getText());
} else {
- throw new PathParsingException(
- "Unsupported comparison value encountered in expression" + ctx.getText());
+ throw new PathParsingException("Unsupported comparison value encountered in expression" + ctx.getText());
}
leafContext(ctx.leafName(), comparisonValue);
}
@@ -140,13 +135,13 @@ public class CpsPathBuilder extends CpsPathBaseListener {
@Override
public void exitTextFunctionCondition(final TextFunctionConditionContext ctx) {
cpsPathQuery.setTextFunctionConditionLeafName(ctx.leafName().getText());
- cpsPathQuery.setTextFunctionConditionValue(stripFirstAndLastCharacter(ctx.StringLiteral().getText()));
+ cpsPathQuery.setTextFunctionConditionValue(unwrapQuotedString(ctx.StringLiteral().getText()));
}
@Override
public void exitContainsFunctionCondition(final CpsPathParser.ContainsFunctionConditionContext ctx) {
cpsPathQuery.setContainsFunctionConditionLeafName(ctx.leafName().getText());
- cpsPathQuery.setContainsFunctionConditionValue(stripFirstAndLastCharacter(ctx.StringLiteral().getText()));
+ cpsPathQuery.setContainsFunctionConditionValue(unwrapQuotedString(ctx.StringLiteral().getText()));
}
@Override
@@ -173,10 +168,6 @@ public class CpsPathBuilder extends CpsPathBaseListener {
return cpsPathQuery;
}
- private static String stripFirstAndLastCharacter(final String wrappedString) {
- return wrappedString.substring(1, wrappedString.length() - 1);
- }
-
@Override
public void exitContainerName(final CpsPathParser.ContainerNameContext ctx) {
final String containerName = ctx.getText();
@@ -207,11 +198,25 @@ public class CpsPathBuilder extends CpsPathBaseListener {
.append(name)
.append(getLastElement(comparativeOperators))
.append("'")
- .append(value)
+ .append(value.toString().replace("'", "''"))
.append("'");
}
- private String getLastElement(final List<String> listOfStrings) {
+ private static String getLastElement(final List<String> listOfStrings) {
return listOfStrings.get(listOfStrings.size() - 1);
}
+
+ private static String unwrapQuotedString(final String wrappedString) {
+ final boolean wasWrappedInSingleQuote = wrappedString.startsWith("'");
+ final String value = stripFirstAndLastCharacter(wrappedString);
+ if (wasWrappedInSingleQuote) {
+ return value.replace("''", "'");
+ } else {
+ return value.replace("\"\"", "\"");
+ }
+ }
+
+ private static String stripFirstAndLastCharacter(final String wrappedString) {
+ return wrappedString.substring(1, wrappedString.length() - 1);
+ }
}
diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
index 78963033d..0017242ab 100644
--- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy
@@ -46,6 +46,10 @@ class CpsPathQuerySpec extends Specification {
'spaces around =' | '/parent/child[@common-leaf-name-int = 5]' || '/parent/child' | 'common-leaf-name-int' | 5
'key in top container' | '/parent[@common-leaf-name-int=5]' || '/parent' | 'common-leaf-name-int' | 5
'parent list' | '/shops/shop[@id=1]/categories[@id=1]/book[@title="Dune"]' || "/shops/shop[@id='1']/categories[@id='1']/book" | 'title' | 'Dune'
+ "' in double quote" | '/parent[@common-leaf-name="leaf\'value"]' || '/parent' | 'common-leaf-name' | "leaf'value"
+ "' in single quote" | "/parent[@common-leaf-name='leaf''value']" || '/parent' | 'common-leaf-name' | "leaf'value"
+ '" in double quote' | '/parent[@common-leaf-name="leaf""value"]' || '/parent' | 'common-leaf-name' | 'leaf"value'
+ '" in single quote' | '/parent[@common-leaf-name=\'leaf"value\']' || '/parent' | 'common-leaf-name' | 'leaf"value'
}
def 'Parse cps path of type ends with a #scenario.'() {
@@ -80,8 +84,8 @@ class CpsPathQuerySpec extends Specification {
'parent leaf of type Integer & child' | '/parent/child[@code=1]/child2' || "/parent/child[@code='1']/child2"
'parent leaf with double quotes' | '/parent/child[@code="1"]/child2' || "/parent/child[@code='1']/child2"
'parent leaf with double quotes inside single quotes' | '/parent/child[@code=\'"1"\']/child2' || "/parent/child[@code='\"1\"']/child2"
- 'parent leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]/child2' || "/parent/child[@code='\\\'1\\\'']/child2"
- 'leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]' || "/parent/child[@code='\\\'1\\\'']"
+ 'parent leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]/child2' || "/parent/child[@code='''1''']/child2"
+ 'leaf with single quotes inside double quotes' | '/parent/child[@code="\'1\'"]' || "/parent/child[@code='''1''']"
'leaf with more than one attribute' | '/parent/child[@key1=1 and @key2="abc"]' || "/parent/child[@key1='1' and @key2='abc']"
'parent & child with more than one attribute' | '/parent/child[@key1=1 and @key2="abc"]/child2' || "/parent/child[@key1='1' and @key2='abc']/child2"
'leaf with more than one attribute has OR operator' | '/parent/child[@key1=1 or @key2="abc"]' || "/parent/child[@key1='1' or @key2='abc']"
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java
index be06ebac0..e371035ba 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentQueryBuilder.java
@@ -136,7 +136,7 @@ public class FragmentQueryBuilder {
final String leafValueAsText = leaf.getValue().toString();
sqlStringBuilder.append("attributes ->> '").append(leaf.getName()).append("'");
sqlStringBuilder.append(" = '");
- sqlStringBuilder.append(leafValueAsText);
+ sqlStringBuilder.append(EscapeUtils.escapeForSqlStringLiteral(leafValueAsText));
sqlStringBuilder.append("'");
} else {
throw new CpsPathException(" can use only " + nextComparativeOperator + " with integer ");
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
index 139a8b306..4c7971ead 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
@@ -31,6 +31,7 @@ import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.spi.utils.EscapeUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@@ -86,7 +87,7 @@ public class TempTableCreator {
final Collection<String> sqlInserts = new HashSet<>(sqlData.size());
for (final Collection<String> rowValues : sqlData) {
final Collection<String> escapedValues =
- rowValues.stream().map(it -> escapeSingleQuotesByDoublingThem(it)).collect(Collectors.toList());
+ rowValues.stream().map(EscapeUtils::escapeForSqlStringLiteral).collect(Collectors.toList());
sqlInserts.add("('" + String.join("','", escapedValues) + "')");
}
sqlStringBuilder.append("INSERT INTO ");
@@ -98,8 +99,4 @@ public class TempTableCreator {
sqlStringBuilder.append(";");
}
- private static String escapeSingleQuotesByDoublingThem(final String value) {
- return value.replace("'", "''");
- }
-
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java b/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java
index 3092b7905..2b61d3950 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/utils/EscapeUtils.java
@@ -26,8 +26,12 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class EscapeUtils {
- public static String escapeForSqlLike(final String text) {
- return text.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
+ public static String escapeForSqlLike(final String value) {
+ return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
+ }
+
+ public static String escapeForSqlStringLiteral(final String value) {
+ return value.replace("'", "''");
}
}
diff --git a/cps-ri/src/main/resources/changelog/changelog-master.yaml b/cps-ri/src/main/resources/changelog/changelog-master.yaml
index 4e6986e71..f76c5ba3b 100644
--- a/cps-ri/src/main/resources/changelog/changelog-master.yaml
+++ b/cps-ri/src/main/resources/changelog/changelog-master.yaml
@@ -56,3 +56,5 @@ databaseChangeLog:
file: changelog/db/changes/19-delete-not-required-dataspace-id-from-fragment.yaml
- include:
file: changelog/db/changes/20-change-foreign-key-id-types-to-integer.yaml
+ - include:
+ file: changelog/db/changes/21-escape-quotes-in-xpath.yaml
diff --git a/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql
new file mode 100644
index 000000000..9bf7f9a74
--- /dev/null
+++ b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-forward.sql
@@ -0,0 +1,19 @@
+/*
+ ============LICENSE_START=======================================================
+ Copyright (C) 2023 Nordix Foundation.
+ ================================================================================
+ Licensed 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.
+ SPDX-License-Identifier: Apache-2.0
+ ============LICENSE_END=========================================================
+*/
+
+-- replace \' with '' and "" with "
+UPDATE fragment SET xpath = replace(replace(xpath, $$\'$$, $$''$$), '""', '"'); \ No newline at end of file
diff --git a/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql
new file mode 100644
index 000000000..0fd1633a5
--- /dev/null
+++ b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql
@@ -0,0 +1,19 @@
+/*
+ ============LICENSE_START=======================================================
+ Copyright (C) 2023 Nordix Foundation.
+ ================================================================================
+ Licensed 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.
+ SPDX-License-Identifier: Apache-2.0
+ ============LICENSE_END=========================================================
+*/
+
+-- replace '' with \' and " with ""
+UPDATE fragment SET xpath = replace(replace(xpath, $$''$$, $$\'$$), '"', '""'); \ No newline at end of file
diff --git a/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml
new file mode 100644
index 000000000..7b5b1dbd0
--- /dev/null
+++ b/cps-ri/src/main/resources/changelog/db/changes/21-escape-quotes-in-xpath.yaml
@@ -0,0 +1,29 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2023 Nordix Foundation.
+# ================================================================================
+# Licensed 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=========================================================
+
+databaseChangeLog:
+
+ - changeSet:
+ id: 21
+ author: cps
+ changes:
+ - sqlFile:
+ path: changelog/db/changes/21-escape-quotes-in-xpath-forward.sql
+ rollback:
+ - sqlFile:
+ path: changelog/db/changes/21-escape-quotes-in-xpath-rollback.sql
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy
index 7de9b97ba..52330e625 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/EscapeUtilsSpec.groovy
@@ -24,7 +24,7 @@ import spock.lang.Specification
class EscapeUtilsSpec extends Specification {
- def 'Escape text for using in SQL LIKE operation'() {
+ def 'Escape text for use in SQL LIKE operation.'() {
expect: 'SQL LIKE special characters to be escaped with forward-slash'
assert EscapeUtils.escapeForSqlLike(unescapedText) == escapedText
where:
@@ -33,4 +33,9 @@ class EscapeUtilsSpec extends Specification {
'Others (./?$) are not special' || 'Others (./?$) are not special'
}
+ def 'Escape text for use in SQL string literal.'() {
+ expect: 'single quotes to be doubled'
+ assert EscapeUtils.escapeForSqlStringLiteral("I'm escaping!") == "I''m escaping!"
+ }
+
}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
index 7da402415..f00f9442c 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
@@ -253,7 +253,7 @@ public class YangUtils {
final List<String> keyAttributes = nodeIdentifier.entrySet().stream().map(
entry -> {
final String name = entry.getKey().getLocalName();
- final String value = String.valueOf(entry.getValue()).replace("'", "\\'");
+ final String value = String.valueOf(entry.getValue()).replace("'", "''");
return String.format("@%s='%s'", name, value);
}
).collect(Collectors.toList());
diff --git a/docs/cps-path.rst b/docs/cps-path.rst
index 796eb7f42..661178954 100644
--- a/docs/cps-path.rst
+++ b/docs/cps-path.rst
@@ -177,6 +177,7 @@ General Notes
=============
- String values must be wrapped in quotation marks ``"`` (U+0022) or apostrophes ``'`` (U+0027).
+- Quotations marks and apostrophes can be escaped by doubling them in their respective quotes, for example ``'CPS ''Path'' Query' -> CPS 'Path' Query``
- String comparisons are case sensitive.
Query Syntax
@@ -247,7 +248,6 @@ leaf-conditions
- The key should be supplied with correct data type for it to be queried from DB. In the last example above the attribute code is of type
Integer so the cps query will not work if the value is passed as string.
eg: ``//categories[@code="1"]`` or ``//categories[@code='1']`` will not work because the key attribute code is treated a string.
- - Having '[' token in any index in any list will have a negative impact on this function.
**Notes**
- For performance reasons it does not make sense to query using key leaf as attribute. If the key value is known it is better to execute a get request with the complete xpath.
@@ -272,7 +272,6 @@ The text()-condition can be added to any CPS path query.
- Only string and integer values are supported, boolean and float values are not supported.
- Since CPS cannot return individual leaves it will always return the container with all its leaves. Ancestor-axis can be used to specify a parent higher up the tree.
- When querying a leaf value (instead of leaf-list) it is better, more performant to use a text value condition use @<leaf-name> as described above.
- - Having '[' token in any index in any list will have a negative impact on this function.
contains()-condition
--------------------
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index f56b34a9f..25f6d22ac 100755
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -42,6 +42,7 @@ Bug Fixes
Features
--------
+ - `CPS-1760 <https://jira.onap.org/browse/CPS-1760>`_ Improve handling of special characters in Cps Paths
Version: 3.3.4
==============
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy
index 53737fba8..74496d301 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy
@@ -356,4 +356,23 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase {
'text-condition' || "/bookstore/categories[@code='1']/books/title[text()='[@hello=world]']"
'contains-condition' || "/bookstore/categories[@code='1']/books[contains(@title, '[@hello=world]')]"
}
+
+ def 'Cps Path get and query can handle apostrophe inside #quotes.'() {
+ given: 'a book with special characters in title'
+ cpsDataService.saveData(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, "/bookstore/categories[@code='1']",
+ '{"books": [ {"title":"I\'m escaping"} ] }', OffsetDateTime.now())
+ when: 'a query is executed'
+ def result = objectUnderTest.queryDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, cpsPath, OMIT_DESCENDANTS)
+ then: 'the node is returned'
+ assert result.size() == 1
+ assert result[0].xpath == "/bookstore/categories[@code='1']/books[@title='I''m escaping']"
+ cleanup: 'the new datanode'
+ cpsDataService.deleteDataNode(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, "/bookstore/categories[@code='1']/books[@title='I''m escaping']", OffsetDateTime.now())
+ where:
+ quotes || cpsPath
+ 'single quotes' || "/bookstore/categories[@code='1']/books[@title='I''m escaping']"
+ 'double quotes' || '/bookstore/categories[@code="1"]/books[@title="I\'m escaping"]'
+ 'text-condition' || "/bookstore/categories[@code='1']/books/title[text()='I''m escaping']"
+ 'contains-condition' || "/bookstore/categories[@code='1']/books[contains(@title, 'I''m escaping')]"
+ }
}