Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CU-86b2tfgf6: MxParseUtils enhancement API to retrieve content by paths #134

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Prowide ISO 20022 - CHANGELOG

#### 9.5.5 - SNAPSHOT
* Added new SettlementInfo class, and added MxParseUtils#getSettlementInfo to extract it from a raw MX message.
* Moved and enhanced the MxSwiftMessage#findElement to support multiple element's path

#### 9.4.7 - August 2024
* (PW-1958) Fixed the `DefaultMxMetadataStrategy` NPE issue when the amount values are null

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.prowidesoftware.swift.model;

/**
* Class for identification of MX messages.
*
* <p>It is composed of the Settlement Method and Clearing System (Code or Property).
* Used in combination with MxId to identify a specific MX message.
*
* @since 9.5.5
*/
public class SettlementInfo {

private SettlementMethod settlementMethod;
private String clrSysCd; // Clearing System Code
private String clrSysPrtry; // Clearing System Proprietary Code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename it to clearingSystemCode and as a single field, we do not care if it comes from code or proprietary


public SettlementInfo() {}

public SettlementMethod getSettlementMethod() {
return settlementMethod;
}

public void setSettlementMethod(SettlementMethod sttlmMtd) {
this.settlementMethod = sttlmMtd;
}

public String getClrSysCd() {
return clrSysCd;
}

public void setClrSysCd(String clrSysCd) {
this.clrSysCd = clrSysCd;
}

public String getClrSysPrtry() {
return clrSysPrtry;
}

public void setClrSysPrtry(String clrSysPrtry) {
this.clrSysPrtry = clrSysPrtry;
}
}
Comment on lines +11 to +42
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider overriding equals, hashCode, and toString methods

For better usability of the SettlementInfo class, especially when using in collections or logging, consider overriding the equals, hashCode, and toString methods.

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.prowidesoftware.swift.model;

import java.util.Arrays;
import java.util.Optional;

/**
* Enum representing different settlement methods for financial transactions.
*
* <p>The settlement methods are:</p>
* <ul>
* <li>{@code INDA} - Instructed Agent</li>
* <li>{@code INGA} - Instructing Agent</li>
* <li>{@code COVE} - Cover Method</li>
* <li>{@code CLRG} - Clearing System</li>
* </ul>
*
* @since 9.5.5
*/
public enum SettlementMethod {
INDA("INDA", "Instructed Agent"),
INGA("INGA", "Instructing Agent"),
COVE("COVE", "Cover Method"),
CLRG("CLRG", "Clearing System");

private final String label;
private final String name;

// Constructor
SettlementMethod(String label, String name) {
this.label = label;
this.name = name;
}

public static Optional<SettlementMethod> findByLabel(String label) {
return Arrays.stream(values())
.filter(method -> method.label.equalsIgnoreCase(label))
.findFirst();
}

public static Optional<SettlementMethod> findByName(String name) {
return Arrays.stream(values())
.filter(method -> method.name.equalsIgnoreCase(name))
.findFirst();
}
Comment on lines +34 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle null inputs in findByLabel and findByName methods

The methods findByLabel and findByName may throw a NullPointerException if the input label or name is null. Consider adding null checks to handle null inputs gracefully.

Apply this diff to add null checks:

    public static Optional<SettlementMethod> findByLabel(String label) {
+       if (label == null) {
+           return Optional.empty();
+       }
        return Arrays.stream(values())
                .filter(method -> method.label.equalsIgnoreCase(label))
                .findFirst();
    }

    public static Optional<SettlementMethod> findByName(String name) {
+       if (name == null) {
+           return Optional.empty();
+       }
        return Arrays.stream(values())
                .filter(method -> method.name.equalsIgnoreCase(name))
                .findFirst();
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static Optional<SettlementMethod> findByLabel(String label) {
return Arrays.stream(values())
.filter(method -> method.label.equalsIgnoreCase(label))
.findFirst();
}
public static Optional<SettlementMethod> findByName(String name) {
return Arrays.stream(values())
.filter(method -> method.name.equalsIgnoreCase(name))
.findFirst();
}
public static Optional<SettlementMethod> findByLabel(String label) {
if (label == null) {
return Optional.empty();
}
return Arrays.stream(values())
.filter(method -> method.label.equalsIgnoreCase(label))
.findFirst();
}
public static Optional<SettlementMethod> findByName(String name) {
if (name == null) {
return Optional.empty();
}
return Arrays.stream(values())
.filter(method -> method.name.equalsIgnoreCase(name))
.findFirst();
}


// Getter for label
public String getLabel() {
return label;
}

// Getter for name
public String getName() {
return name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@
import com.prowidesoftware.ProwideException;
import com.prowidesoftware.swift.model.DistinguishedName;
import com.prowidesoftware.swift.model.MxId;
import com.prowidesoftware.swift.model.SettlementInfo;
import com.prowidesoftware.swift.model.SettlementMethod;
Comment on lines +23 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Use narrower imports if needed

Consider whether you need both imports for SettlementInfo and SettlementMethod in this class, or if some are only used in a single method or for testing. Reducing imports to only those necessary helps maintain clarity.

import com.prowidesoftware.swift.utils.SafeXmlUtils;
import java.io.StringReader;
import java.util.Objects;
import java.util.Optional;
import java.util.Stack;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Just a note on concurrency considerations

The newly introduced Stack is only used locally within one method. Although safe in this specific scenario, be aware that Stack is generally synchronized but can lead to performance overhead or concurrency pitfalls if used in multi-threaded contexts without caution.

import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.sax.SAXSource;
Expand Down Expand Up @@ -198,10 +204,10 @@ public static Optional<MxId> identifyMessage(final String xml) {
}

// if the Document does not have a namespace, try to identify the message from the header
Optional<XMLStreamReader> element = NamespaceReader.findElement(xml, "MsgDefIdr");
Optional<XMLStreamReader> element = findElementByTags(xml, "MsgDefIdr");
if (!element.isPresent()) {
// Legacy ahv10 header
element = NamespaceReader.findElement(xml, "MsgName");
element = findElementByTags(xml, "MsgName");
}
if (element.isPresent()) {
try {
Expand All @@ -218,7 +224,7 @@ private static Optional<MxId> enrichBusinessService(MxId mxId, final String xml)
if (mxId == null) {
return Optional.empty();
}
Optional<XMLStreamReader> element = NamespaceReader.findElement(xml, "BizSvc");
Optional<XMLStreamReader> element = findElementByTags(xml, "BizSvc");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Potential performance minor improvement

When searching for "BizSvc", you might want to break early if found, to avoid scanning further. Currently it breaks after the first match, so it is fine but consider the overhead if a second or further match is found.

if (element.isPresent()) {
try {
mxId.setBusinessService(element.get().getElementText());
Expand All @@ -241,4 +247,173 @@ private static Optional<MxId> enrichBusinessService(MxId mxId, final String xml)
public static String makeXmlLenient(String xml) {
return xml != null ? xml.replaceFirst("(?i)<\\?XML", "<?xml") : null;
}

/**
* Extracts settlement information from the given XML document.
*
* <p>This method attempts to parse the provided XML and extract information
* related to the settlement method and clearing system codes. Specifically:
* <ul>
* <li>{@code SttlmMtd} - The settlement method.</li>
* <li>{@code ClrSys > Cd} - The clearing system code.</li>
* <li>{@code ClrSys > Prtry} - The clearing system proprietary.</li>
* </ul>
*
* <p>If any of these elements are found, a {@link SettlementInfo} object is
* created and populated with the extracted values.
*
* @param xml the XML document as a {@link String} to parse for settlement information.
* @return an {@link Optional} containing the {@link SettlementInfo} if at least one
* of the required elements is found; otherwise, an empty {@link Optional}.
* @throws NullPointerException if the {@code xml} is null.
* @since 9.5.5
*/
public static Optional<SettlementInfo> getSettlementInfo(final String xml) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check for xml parameter in getSettlementInfo method

The method getSettlementInfo should explicitly check if xml is null to prevent potential NullPointerException, as indicated in the method documentation.

Apply this diff to add the null check:

+        Objects.requireNonNull(xml, "XML to parse must not be null");
         Optional<XMLStreamReader> sttlmMtdMaybe = findElement(xml, "SttlmMtd");

Committable suggestion skipped: line range outside the PR's diff.


Optional<XMLStreamReader> sttlmMtdMaybe = findElementByTags(xml, "SttlmMtd");
Optional<XMLStreamReader> clrSysCdMaybe = findElementByTags(xml, "ClrSys", "Cd");
Optional<XMLStreamReader> clrSysPrtryMaybe = findElementByTags(xml, "ClrSys", "Prtry");

if (sttlmMtdMaybe.isPresent() || clrSysCdMaybe.isPresent() || clrSysPrtryMaybe.isPresent()) {
SettlementInfo settlementInfo = new SettlementInfo();
try {
if (sttlmMtdMaybe.isPresent()) {

Optional<SettlementMethod> sttlmMtdLabel =
SettlementMethod.findByLabel(sttlmMtdMaybe.get().getElementText());
sttlmMtdLabel.ifPresent(settlementInfo::setSettlementMethod);
}
if (clrSysCdMaybe.isPresent()) {
settlementInfo.setClrSysCd(clrSysCdMaybe.get().getElementText());
}
if (clrSysPrtryMaybe.isPresent()) {
settlementInfo.setClrSysPrtry(clrSysPrtryMaybe.get().getElementText());
}
return Optional.of(settlementInfo);
} catch (XMLStreamException e) {
log.finer("Error identifying business service: " + e.getMessage());
}
}
return Optional.empty();
}
Comment on lines +373 to +420
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing null check and improve error handling.

Several issues need to be addressed:

  1. Missing null check for xml parameter
  2. Incorrect error message in catch block ("Error identifying business service" should be "Error extracting settlement information")

Apply this diff to fix the issues:

    public static Optional<SettlementInfo> getSettlementInfo(final String xml) {
+       Objects.requireNonNull(xml, "XML to parse must not be null");
+       Validate.notBlank(xml, "XML to parse must not be a blank string");

        Optional<XMLStreamReader> sttlmMtdMaybe = findElementByTags(xml, "SttlmMtd");
        Optional<XMLStreamReader> clrSysCdMaybe = findElementByTags(xml, "ClrSys", "Cd");
        Optional<XMLStreamReader> clrSysPrtryMaybe = findElementByTags(xml, "ClrSys", "Prtry");

        if (sttlmMtdMaybe.isPresent() || clrSysCdMaybe.isPresent() || clrSysPrtryMaybe.isPresent()) {
            SettlementInfo settlementInfo = new SettlementInfo();
            try {
                if (sttlmMtdMaybe.isPresent()) {
                    Optional<SettlementMethod> sttlmMtdLabel =
                            SettlementMethod.findByLabel(sttlmMtdMaybe.get().getElementText());
                    sttlmMtdLabel.ifPresent(settlementInfo::setSettlementMethod);
                }
                if (clrSysCdMaybe.isPresent()) {
                    settlementInfo.setClrSysCd(clrSysCdMaybe.get().getElementText());
                }
                if (clrSysPrtryMaybe.isPresent()) {
                    settlementInfo.setClrSysPrtry(clrSysPrtryMaybe.get().getElementText());
                }
                return Optional.of(settlementInfo);
            } catch (XMLStreamException e) {
-               log.finer("Error identifying business service: " + e.getMessage());
+               log.finer("Error extracting settlement information: " + e.getMessage());
            }
        }
        return Optional.empty();
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Extracts settlement information from the given XML document.
*
* <p>This method attempts to parse the provided XML and extract information
* related to the settlement method and clearing system codes. Specifically:
* <ul>
* <li>{@code SttlmMtd} - The settlement method.</li>
* <li>{@code ClrSys > Cd} - The clearing system code.</li>
* <li>{@code ClrSys > Prtry} - The clearing system proprietary.</li>
* </ul>
*
* <p>If any of these elements are found, a {@link SettlementInfo} object is
* created and populated with the extracted values.
*
* @param xml the XML document as a {@link String} to parse for settlement information.
* @return an {@link Optional} containing the {@link SettlementInfo} if at least one
* of the required elements is found; otherwise, an empty {@link Optional}.
* @throws NullPointerException if the {@code xml} is null.
* @since 9.5.5
*/
public static Optional<SettlementInfo> getSettlementInfo(final String xml) {
Optional<XMLStreamReader> sttlmMtdMaybe = findElementByTags(xml, "SttlmMtd");
Optional<XMLStreamReader> clrSysCdMaybe = findElementByTags(xml, "ClrSys", "Cd");
Optional<XMLStreamReader> clrSysPrtryMaybe = findElementByTags(xml, "ClrSys", "Prtry");
if (sttlmMtdMaybe.isPresent() || clrSysCdMaybe.isPresent() || clrSysPrtryMaybe.isPresent()) {
SettlementInfo settlementInfo = new SettlementInfo();
try {
if (sttlmMtdMaybe.isPresent()) {
Optional<SettlementMethod> sttlmMtdLabel =
SettlementMethod.findByLabel(sttlmMtdMaybe.get().getElementText());
sttlmMtdLabel.ifPresent(settlementInfo::setSettlementMethod);
}
if (clrSysCdMaybe.isPresent()) {
settlementInfo.setClrSysCd(clrSysCdMaybe.get().getElementText());
}
if (clrSysPrtryMaybe.isPresent()) {
settlementInfo.setClrSysPrtry(clrSysPrtryMaybe.get().getElementText());
}
return Optional.of(settlementInfo);
} catch (XMLStreamException e) {
log.finer("Error identifying business service: " + e.getMessage());
}
}
return Optional.empty();
}
/**
* Extracts settlement information from the given XML document.
*
* <p>This method attempts to parse the provided XML and extract information
* related to the settlement method and clearing system codes. Specifically:
* <ul>
* <li>{@code SttlmMtd} - The settlement method.</li>
* <li>{@code ClrSys > Cd} - The clearing system code.</li>
* <li>{@code ClrSys > Prtry} - The clearing system proprietary.</li>
* </ul>
*
* <p>If any of these elements are found, a {@link SettlementInfo} object is
* created and populated with the extracted values.
*
* @param xml the XML document as a {@link String} to parse for settlement information.
* @return an {@link Optional} containing the {@link SettlementInfo} if at least one
* of the required elements is found; otherwise, an empty {@link Optional}.
* @throws NullPointerException if the {@code xml} is null.
* @since 9.5.5
*/
public static Optional<SettlementInfo> getSettlementInfo(final String xml) {
Objects.requireNonNull(xml, "XML to parse must not be null");
Validate.notBlank(xml, "XML to parse must not be a blank string");
Optional<XMLStreamReader> sttlmMtdMaybe = findElementByTags(xml, "SttlmMtd");
Optional<XMLStreamReader> clrSysCdMaybe = findElementByTags(xml, "ClrSys", "Cd");
Optional<XMLStreamReader> clrSysPrtryMaybe = findElementByTags(xml, "ClrSys", "Prtry");
if (sttlmMtdMaybe.isPresent() || clrSysCdMaybe.isPresent() || clrSysPrtryMaybe.isPresent()) {
SettlementInfo settlementInfo = new SettlementInfo();
try {
if (sttlmMtdMaybe.isPresent()) {
Optional<SettlementMethod> sttlmMtdLabel =
SettlementMethod.findByLabel(sttlmMtdMaybe.get().getElementText());
sttlmMtdLabel.ifPresent(settlementInfo::setSettlementMethod);
}
if (clrSysCdMaybe.isPresent()) {
settlementInfo.setClrSysCd(clrSysCdMaybe.get().getElementText());
}
if (clrSysPrtryMaybe.isPresent()) {
settlementInfo.setClrSysPrtry(clrSysPrtryMaybe.get().getElementText());
}
return Optional.of(settlementInfo);
} catch (XMLStreamException e) {
log.finer("Error extracting settlement information: " + e.getMessage());
}
}
return Optional.empty();
}


/**
* Finds an XML element within a document by traversing a specified tag hierarchy.
*
* <p>This method uses an {@link XMLStreamReader} to parse the provided XML document.
* It searches for an element that matches the specified sequence of tag names (hierarchy).
* For example, to find the {@code <Cd>} tag within {@code <ClrSys>}, you would call:
* <pre>
* findElement(xml, "ClrSys", "Cd");
* </pre>
*
* @param xml the XML document as a {@link String} to search.
* @param tags the sequence of tag names that define the hierarchy of the target element.
* @return an {@link Optional} containing the {@link XMLStreamReader} positioned at the
* matching element if found; otherwise, an empty {@link Optional}.
* @throws NullPointerException if the {@code xml} or {@code tags} are null.
* @throws IllegalArgumentException if the {@code xml} is a blank string.
* @since 9.5.5
*/
public static Optional<XMLStreamReader> findElementByTags(final String xml, String... tags) {
Objects.requireNonNull(xml, "XML to parse must not be null");
Validate.notBlank(xml, "XML to parse must not be a blank string");
Objects.requireNonNull(xml, "tags to find must not be null");

final XMLInputFactory xif = SafeXmlUtils.inputFactory();
int tagsIndex = 0;
try {
final XMLStreamReader reader =
xif.createXMLStreamReader(new StringReader(MxParseUtils.makeXmlLenient(xml)));
while (reader.hasNext()) {
int event = reader.next();
if (XMLStreamConstants.START_ELEMENT == event) {
if (reader.getLocalName().equals(tags[tagsIndex])) {
if (tagsIndex == tags.length - 1) {
return Optional.of(reader);
}
tagsIndex++;
}
}
}
} catch (XMLStreamException e) {
log.log(Level.WARNING, "Error reading XML", e);
}
return Optional.empty();
}
Comment on lines +448 to +465
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure XMLStreamReader is properly closed to prevent resource leaks

The XMLStreamReader should be closed after usage to prevent resource leaks. Consider using a try-with-resources statement to automatically close the reader.

Apply this diff to use try-with-resources:

        try {
-            final XMLStreamReader reader =
-                    xif.createXMLStreamReader(new StringReader(MxParseUtils.makeXmlLenient(xml)));
+            try (final XMLStreamReader reader =
+                    xif.createXMLStreamReader(new StringReader(MxParseUtils.makeXmlLenient(xml)))) {
                while (reader.hasNext()) {
                    int event = reader.next();
                    if (XMLStreamConstants.START_ELEMENT == event) {
                        if (reader.getLocalName().equals(tags[tagsIndex])) {
                            if (tagsIndex == tags.length - 1) {
                                return Optional.of(reader);
                            }
                            tagsIndex++;
                        }
                    }
                }
            }
        } catch (XMLStreamException e) {
            log.log(Level.WARNING, "Error reading XML", e);
        }

Committable suggestion skipped: line range outside the PR's diff.


/**
* Finds an XML element within a document by traversing a specified tag hierarchy.
*
* <p>This method uses an {@link XMLStreamReader} to parse the provided XML document.
* It searches for an element that matches the specified sequence of tag names (hierarchy).
* For example, to find the {@code <Cd>} tag within {@code <ClrSys>}, you would call:
* <pre>
* findElement(xml, "ClrSys", "Cd");
* </pre>
*
* @param xml the XML document as a {@link String} to search.
* @param targetPath the path of the field to find into the xml.
* @return an {@link Optional} containing the {@link XMLStreamReader} positioned at the
* matching element if found; otherwise, an empty {@link Optional}.
* @throws NullPointerException if the {@code xml} or {@code tags} are null.
* @throws IllegalArgumentException if the {@code xml} is a blank string.
* @since 9.5.5
*/
public static Optional<XMLStreamReader> findElementByPath(String xml, String targetPath) {
Objects.requireNonNull(xml, "XML to parse must not be null");
Validate.notBlank(xml, "XML to parse must not be a blank string");
Objects.requireNonNull(xml, "targetPath to find must not be null");
XMLInputFactory factory = XMLInputFactory.newInstance();

try {

final XMLStreamReader reader =
factory.createXMLStreamReader(new StringReader(MxParseUtils.makeXmlLenient(xml)));

Stack<String> pathStack = new Stack<>();

while (reader.hasNext()) {
int event = reader.next();

switch (event) {
case XMLStreamConstants.START_ELEMENT:
// Push the current element onto the path stack
pathStack.push(reader.getLocalName());
// Build the current path
String currentPath = buildCurrentPath(pathStack);

// Check if the current path matches the target path
if (currentPath.equals(targetPath)) {
return Optional.of(reader);
}
break;

case XMLStreamConstants.END_ELEMENT:
// Pop the element from the path stack
if (!pathStack.isEmpty()) {
pathStack.pop();
}
break;

default:
break;
}
}
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}

return Optional.empty(); // Return empty if the path is not found
}

/**
* \
* @param pathStack
* @return the current path
* Join the stack elements with "/" to form the current path
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Fix incomplete and malformed Javadoc comment in buildCurrentPath method

At lines 411-415, the Javadoc comment is incomplete and contains an unnecessary backslash, which may cause documentation generation issues.

Apply this diff to correct the Javadoc:

-    /**
-     * \
-     * @param pathStack
-     * @return the current path
-     * Join the stack elements with "/" to form the current path
-     */
+    /**
+     * Joins the stack elements with "/" to form the current XML path.
+     *
+     * @param pathStack the stack containing XML element names
+     * @return the current path as a String
+     */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* \
* @param pathStack
* @return the current path
* Join the stack elements with "/" to form the current path
*/
/**
* Joins the stack elements with "/" to form the current XML path.
*
* @param pathStack the stack containing XML element names
* @return the current path as a String
*/

private static String buildCurrentPath(Stack<String> pathStack) {
return "/" + String.join("/", pathStack);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,12 @@
*/
package com.prowidesoftware.swift.model.mx;

import com.prowidesoftware.swift.utils.SafeXmlUtils;
import java.io.StringReader;
import java.util.Objects;
import static com.prowidesoftware.swift.model.mx.MxParseUtils.findElementByTags;

import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

/**
* Helper API to extract information from an XML using lightweight XML streams API
Expand Down Expand Up @@ -64,7 +58,7 @@ public static Optional<String> findAppHdrNamespace(final String xml) {
* @return found namespace or empty if the element is not found or does not contain a namespace
*/
public static Optional<String> findNamespaceForLocalName(final String xml, final String localName) {
Optional<XMLStreamReader> reader = findElement(xml, localName);
Optional<XMLStreamReader> reader = findElementByTags(xml, localName);
return reader.map(NamespaceReader::readNamespace);
}

Expand Down Expand Up @@ -95,29 +89,6 @@ private static String readNamespace(final XMLStreamReader reader) {
* @return true if at least one element with the given name is found
*/
public static boolean elementExists(final String xml, final String localName) {
return findElement(xml, localName).isPresent();
}

static Optional<XMLStreamReader> findElement(final String xml, final String localName) {
Objects.requireNonNull(xml, "XML to parse must not be null");
Validate.notBlank(xml, "XML to parse must not be a blank string");
Objects.requireNonNull(xml, "localName to find must not be null");

final XMLInputFactory xif = SafeXmlUtils.inputFactory();
try {
final XMLStreamReader reader =
xif.createXMLStreamReader(new StringReader(MxParseUtils.makeXmlLenient(xml)));
while (reader.hasNext()) {
int event = reader.next();
if (XMLStreamConstants.START_ELEMENT == event) {
if (reader.getLocalName().equals(localName)) {
return Optional.of(reader);
}
}
}
} catch (XMLStreamException e) {
log.log(Level.WARNING, "Error reading XML", e);
}
return Optional.empty();
return findElementByTags(xml, localName).isPresent();
}
}
Loading