com.prowidesoftware.swift.io.parser.XMLParser Maven / Gradle / Ivy
Show all versions of pw-swift-core Show documentation
/*
* Copyright 2006-2023 Prowide
*
* 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.
*/
package com.prowidesoftware.swift.io.parser;
import com.prowidesoftware.swift.io.writer.FINWriterVisitor;
import com.prowidesoftware.swift.model.*;
import com.prowidesoftware.swift.model.field.Field;
import com.prowidesoftware.swift.utils.SafeXmlUtils;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import javax.xml.parsers.DocumentBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* This is the main parser for WIFE's XML internal representation.
* The supported XML format is consider internal because it is an ad-hoc
* defined XML structure for Swift messages, so it's not the SWIFT XML
* Standard for FIN Messages.
*
*
* This implementation should be used by calling some of the the conversion
* services.
*
* @author sebastian
* @see com.prowidesoftware.swift.io.IConversionService
* @since 5.0
*/
public class XMLParser {
private static final transient java.util.logging.Logger log =
java.util.logging.Logger.getLogger(XMLParser.class.getName());
private static final String UNPARSEDTEXTS = "unparsedtexts";
/**
* Given a String containing a message in its WIFE internal XML
* representation, returns a SwiftMessage object.
* If there is any error during conversion this method returns null
*
* @param xml the string containing the XML to parse
* @return the XML parsed into a SwiftMessage object
* @see com.prowidesoftware.swift.io.IConversionService#getMessageFromXML(java.lang.String)
*/
public SwiftMessage parse(final String xml) {
Validate.isTrue(xml != null);
try {
final DocumentBuilder db = SafeXmlUtils.documentBuilder();
final Document doc = db.parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
return createMessage(doc);
} catch (final Exception e) {
log.log(Level.WARNING, "Error parsing XML", e);
return null;
}
}
/**
* Helper method for XML representation parsing.
*
* @param doc Document object containing a message in XML format
* @return SwiftMessage object populated with the given XML message data
*/
private SwiftMessage createMessage(final Document doc) {
final NodeList messageNL = doc.getElementsByTagName("message");
if (messageNL.getLength() == 1) {
final Node message = messageNL.item(0);
final SwiftMessage m = new SwiftMessage(false);
final NodeList blocksNL = message.getChildNodes();
if (log.isLoggable(Level.FINE)) {
log.fine("blocks in message: " + blocksNL.getLength());
}
for (int i = 0; i < blocksNL.getLength(); i++) {
final Node blockNode = blocksNL.item(i);
if (log.isLoggable(Level.FINE)) {
log.fine("evaluating node " + blockNode.getNodeName());
}
if (blockNode.getNodeType() == Node.ELEMENT_NODE) {
final String blockName = blockNode.getNodeName();
if ("block1".equalsIgnoreCase(blockName)) {
m.setBlock1(getBlock1FromNode(blockNode));
} else if ("block2".equalsIgnoreCase(blockName)) {
m.setBlock2(getBlock2FromNode(blockNode));
} else if (UNPARSEDTEXTS.equalsIgnoreCase(blockName)) {
// unparsed texts at level
m.setUnparsedTexts(getUnparsedTextsFromNode(blockNode));
} else {
// blocks 3, 4, 5 or user blocks
m.addBlock(getTagListBlockFromNode(blockNode));
}
}
} // end block list iteration
return m;
} else {
throw new IllegalArgumentException(" tag not found");
}
}
/**
* Helper method for XML representation parsing.
* Given the <block1> node in the XML tree, returns the SwiftBlock1 object.
*
* @param blockNode Node object of the <block1> tag in the XML message
* @return SwiftBlock1 object populated with the given portion of the XML message
*/
private SwiftBlock1 getBlock1FromNode(final Node blockNode) {
final NodeList fields = blockNode.getChildNodes();
if (log.isLoggable(Level.FINE)) {
log.fine(fields.getLength() + " children in ");
}
final SwiftBlock1 b1 = new SwiftBlock1();
for (int i = 0; i < fields.getLength(); i++) {
final Node n = fields.item(i);
if ("APPLICATIONID".equalsIgnoreCase(n.getNodeName())) {
b1.setApplicationId(getText(n));
} else if ("SERVICEID".equalsIgnoreCase(n.getNodeName())) {
b1.setServiceId(getText(n));
} else if ("LOGICALTERMINAL".equalsIgnoreCase(n.getNodeName())) {
b1.setLogicalTerminal(getText(n));
} else if ("SESSIONNUMBER".equalsIgnoreCase(n.getNodeName())) {
b1.setSessionNumber(getText(n));
} else if ("SEQUENCENUMBER".equalsIgnoreCase(n.getNodeName())) {
b1.setSequenceNumber(getText(n));
} else if (UNPARSEDTEXTS.equalsIgnoreCase(n.getNodeName())) {
b1.setUnparsedTexts(getUnparsedTextsFromNode(n));
}
}
return b1;
}
private String getText(final Node n) {
String text = null;
final Node c = n.getFirstChild();
if (c != null) {
if (c.getNodeType() == Node.TEXT_NODE) {
text = c.getNodeValue();
} else {
log.warning("Node is not TEXT_NODE: " + c);
}
}
return text;
}
/**
* Helper method for XML representation parsing.
* Given the <block2> node in the XML tree, returns the SwiftBlock1 object.
* The method checks for the "type" attribute in the <block2> tag and
* returns a SwiftBlock2Input or SwiftBlock2Output.
*
* @param blockNode Node object of the <block2> tag in the XML message
* @return SwiftBlock2 object populated with the given portion of the XML message
* @see #getBlock2InputFromNode(Node)
* @see #getBlock2OutputFromNode(Node)
*/
private SwiftBlock2 getBlock2FromNode(final Node blockNode) {
final String type = getNodeAttribute(blockNode, "type");
if (type == null) {
log.severe("atrribute 'type' was expected but not found at xml tag");
return null;
} else if ("input".equals(type)) {
return getBlock2InputFromNode(blockNode);
} else if ("output".equals(type)) {
return getBlock2OutputFromNode(blockNode);
} else {
log.severe(
"expected 'input' or 'output' value for 'type' atribute at xml tag, and found: " + type);
return null;
}
}
/**
* Helper method for XML representation parsing.
* Given the <block2 type="input"> node in the XML tree, returns the
* SwiftBlock2Input object.
*
* @param blockNode Node object of the <block2> tag in the XML message
* @return SwiftBlock2Input object populated with the given portion of the XML message
*/
private SwiftBlock2Input getBlock2InputFromNode(final Node blockNode) {
final NodeList fields = blockNode.getChildNodes();
if (log.isLoggable(Level.FINE)) {
log.fine(fields.getLength() + " childrens in ");
}
final SwiftBlock2Input b2 = new SwiftBlock2Input();
for (int i = 0; i < fields.getLength(); i++) {
final Node n = fields.item(i);
if ("MESSAGETYPE".equalsIgnoreCase(n.getNodeName())) {
b2.setMessageType(getText(n));
} else if ("RECEIVERADDRESS".equalsIgnoreCase(n.getNodeName())) {
b2.setReceiverAddress(getText(n));
} else if ("MESSAGEPRIORITY".equalsIgnoreCase(n.getNodeName())) {
b2.setMessagePriority(getText(n));
} else if ("DELIVERYMONITORING".equalsIgnoreCase(n.getNodeName())) {
b2.setDeliveryMonitoring(getText(n));
} else if ("OBSOLESCENCEPERIOD".equalsIgnoreCase(n.getNodeName())) {
b2.setObsolescencePeriod(getText(n));
} else if (UNPARSEDTEXTS.equalsIgnoreCase(n.getNodeName())) {
b2.setUnparsedTexts(getUnparsedTextsFromNode(n));
}
}
return b2;
}
/**
* Helper method for XML representation parsing.
* Given the <block2 type="output" node in the XML tree, returns the
* SwiftBlock2Output object.
*
* @param blockNode Node object of the <block2> tag in the XML message
* @return SwiftBlock2Output object populated with the given portion of the XML message
*/
private SwiftBlock2Output getBlock2OutputFromNode(final Node blockNode) {
final NodeList fields = blockNode.getChildNodes();
if (log.isLoggable(Level.FINE)) {
log.fine(fields.getLength() + " childrens in ");
}
final SwiftBlock2Output b2 = new SwiftBlock2Output();
for (int i = 0; i < fields.getLength(); i++) {
final Node n = fields.item(i);
if ("MESSAGETYPE".equalsIgnoreCase(n.getNodeName())) {
b2.setMessageType(getText(n));
} else if ("SENDERINPUTTIME".equalsIgnoreCase(n.getNodeName())) {
b2.setSenderInputTime(getText(n));
} else if ("MIRDATE".equalsIgnoreCase(n.getNodeName())) {
b2.setMIRDate(getText(n));
} else if ("MIRLOGICALTERMINAL".equalsIgnoreCase(n.getNodeName())) {
b2.setMIRLogicalTerminal(getText(n));
} else if ("MIRSESSIONNUMBER".equalsIgnoreCase(n.getNodeName())) {
b2.setMIRSessionNumber(getText(n));
} else if ("MIRSEQUENCENUMBER".equalsIgnoreCase(n.getNodeName())) {
b2.setMIRSequenceNumber(getText(n));
} else if ("RECEIVEROUTPUTDATE".equalsIgnoreCase(n.getNodeName())) {
b2.setReceiverOutputDate(getText(n));
} else if ("RECEIVEROUTPUTTIME".equalsIgnoreCase(n.getNodeName())) {
b2.setReceiverOutputTime(getText(n));
} else if ("MESSAGEPRIORITY".equalsIgnoreCase(n.getNodeName())) {
b2.setMessagePriority(getText(n));
} else if (UNPARSEDTEXTS.equalsIgnoreCase(n.getNodeName())) {
b2.setUnparsedTexts(getUnparsedTextsFromNode(n));
}
}
return b2;
}
/**
* Helper method for XML representation parsing.
* Given the <block3>, <block4>, <block5> or <block> (user block) node in
* the XML tree, returns the corresponding SwiftTagListBlock object
* populated with the given portion of the XML message.
*
* @param blockNode Node object of the <block3>, <block4>, <block5> or <block> tag in the XML message
* @return SwiftTagListBlock object populated with the given portion of the XML message
*/
private SwiftTagListBlock getTagListBlockFromNode(final Node blockNode) {
final String blockName = blockNode.getNodeName();
SwiftTagListBlock b;
if ("block3".equalsIgnoreCase(blockName)) {
b = new SwiftBlock3();
} else if ("block4".equalsIgnoreCase(blockName)) {
b = new SwiftBlock4();
} else if ("block5".equalsIgnoreCase(blockName)) {
b = new SwiftBlock5();
} else if ("block".equalsIgnoreCase(blockName)) {
final String name = getNodeAttribute(blockNode, "name");
if (name != null) {
b = new SwiftBlockUser(name);
} else {
b = new SwiftBlockUser();
}
} else {
return null;
}
final NodeList fields = blockNode.getChildNodes();
if (log.isLoggable(Level.FINE)) {
log.fine(fields.getLength() + " children in tag list " + blockName);
}
for (int j = 0; j < fields.getLength(); j++) {
final Node t = fields.item(j);
if ("tag".equalsIgnoreCase(t.getNodeName())) {
final Tag tag = getTag(t);
b.append(tag);
} else if ("field".equalsIgnoreCase(t.getNodeName())) {
final Field field = getField(t);
b.append(field);
} else if (UNPARSEDTEXTS.equalsIgnoreCase(t.getNodeName())) {
b.setUnparsedTexts(getUnparsedTextsFromNode(t));
}
}
return b;
}
/**
* Helper method for XML representation parsing.
* Parses the given <tag> Node and returns a Tag object containing data from
* the expected <name> and <value> tags. If name or value are not found as
* children of the given node, the Tag object is returned with empty values.
*
* @param t the XML node to parse for name-value pair
* @return a Tag object containing the name and value of the given XML node.
*/
private Tag getTag(final Node t) {
final Tag tag = new Tag();
final NodeList children = t.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
final Node n = children.item(i);
if ("name".equalsIgnoreCase(n.getNodeName())) {
tag.setName(getText(n));
}
if ("value".equalsIgnoreCase(n.getNodeName())) {
String text = getText(n);
// normalize line feeds (DOM parser removes carriage return characters from original XML file)
text = StringUtils.replace(text, "\n", FINWriterVisitor.SWIFT_EOL);
tag.setValue(text);
} else if (UNPARSEDTEXTS.equalsIgnoreCase(n.getNodeName())) {
tag.setUnparsedTexts(getUnparsedTextsFromNode(n));
}
}
return tag;
}
/**
* Helper method for XML representation parsing.
* Parses the given >field< Node and returns a Field object containing data from
* the expected <name> and <component> inner elements.
* If <name> element is not set it will return null. Otherwise it will return a Field
* instance filled with content from <component> elements.
*
* @param t the XML node to parse for name-value pair
* @return a Field object or null if "name" element is not present
*/
private Field getField(final Node t) {
final NodeList children = t.getChildNodes();
String name = null;
for (int i = 0; i < children.getLength(); i++) {
final Node n = children.item(i);
if ("name".equalsIgnoreCase(n.getNodeName())) {
name = getText(n);
break;
}
}
if (name != null) {
Field field = Field.getField(name, null);
for (int i = 0; i < children.getLength(); i++) {
final Node n = children.item(i);
if ("component".equalsIgnoreCase(n.getNodeName())) {
final String number = getNodeAttribute(n, "number");
if (StringUtils.isNumeric(number)) {
String text = getText(n);
// normalize line feeds (DOM parser removes carriage return characters from original XML file)
text = StringUtils.replace(text, "\n", FINWriterVisitor.SWIFT_EOL);
try {
field.setComponent(Integer.parseInt(number), text);
} catch (NumberFormatException e) {
log.warning(
"error setting component " + number + " for field " + name + ": " + e.getMessage());
return null;
}
}
}
}
return field;
}
return null;
}
/**
* Helper method for XML representation parsing.
* Given the <unparsedtexts> node in the XML tree, returns an
* UnparsedTextList object populated with the contents of the child
* tags of <unparsedtexts>.
*
* @param blockNode Node object of the <unparsedtexts> tag in the XML message
* @return UnparsedTextList object populated with the given <text> tags content of the <unparsedtexts>
*/
private UnparsedTextList getUnparsedTextsFromNode(final Node blockNode) {
final UnparsedTextList unparsedTexts = new UnparsedTextList();
final NodeList texts = blockNode.getChildNodes();
if (log.isLoggable(Level.FINE)) {
log.fine(texts.getLength() + " children in ");
}
for (int j = 0; j < texts.getLength(); j++) {
final Node t = texts.item(j);
if ("text".equalsIgnoreCase(t.getNodeName())) {
unparsedTexts.addText(getText(t));
}
}
return unparsedTexts;
}
/**
* Helper method for XML representation parsing.
* Gets the value of an expected attribute in a Node.
*
* @param n Node to analyze to find the attribute
* @param attributeName the attribute name expected in the analyzed Node n
* @return the value of the attribute expected, or null if the attribute was not found
*/
private String getNodeAttribute(final Node n, final String attributeName) {
final Node attr = n.getAttributes().getNamedItem(attributeName);
if (attr == null || !attr.getNodeName().equals(attributeName)) {
return null;
} else {
return attr.getNodeValue();
}
}
}