All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.regnosys.rosetta.translate.XMLParser Maven / Gradle / Ivy

There is a newer version: 11.25.1
Show newest version
package com.regnosys.rosetta.translate;

import java.io.CharArrayReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.regnosys.rosetta.common.hashing.ReferenceConfig;
import com.regnosys.rosetta.common.translation.Mapping;
import com.regnosys.rosetta.common.translation.MappingProcessor;
import com.regnosys.rosetta.common.translation.Path;
import com.regnosys.rosetta.common.translation.Path.PathElement;
import com.regnosys.rosetta.translate.ParserResult.Context;
import com.rosetta.model.lib.RosettaModelObject;
import com.rosetta.model.lib.RosettaModelObjectBuilder;
import com.rosetta.model.lib.path.RosettaPath;

public class XMLParser extends ParserParent {

	private static final Logger LOGGER = LoggerFactory.getLogger(XMLParser.class);
	
	public XMLParser(HandlerFactory factory, SynonymToEnumMapBuilder synonymToEnumMap, ReferenceConfig referenceConfig) {
		super(factory, synonymToEnumMap, referenceConfig);
	}

	@Override
	protected  ParserResult parseTheDocument(Reader xmlReader, Schema schema, Class returnClass) {		
		char[] xmlChars = ParserHelper.toCharsfromReader(xmlReader);
		List validationErrors = validateXML(xmlChars, schema);
		Context context = new Context(synonymToEnumMap);
		
		Map, HandlerSupplier> handlers = factory.getHandlerSuppliers(context.getMappingContext());
		if (!handlers.containsKey(returnClass)) {
			throw new IngestException("Do not have parser for " + returnClass.getSimpleName());
		}
		
		if (validationErrors.isEmpty()) {
			try (Reader xml = new CharArrayReader(xmlChars)) {
				ROMParseHandler handler = (ROMParseHandler) handlers.get(returnClass).apply(new Path(), new Path());
				handler.setRosettaPath(new Path().addElement(new PathElement(returnClass.getSimpleName())));
				
				XMLInputFactory inputFac = createXmlInputFactory();
				XMLStreamReader xmlStreamReader = inputFac.createXMLStreamReader(xml);

				parse(xmlStreamReader, handler, context);
				
				RosettaModelObjectBuilder rosettaInstance = handler.getUnderlying();
				
				LOGGER.debug("Finished parsing");
				return new ParserResult(rosettaInstance, context);
			} catch (XMLStreamException | IOException ioe) {
				throw new IngestException("error reading input xml", ioe);
			}
		}
		context.getXmlValidationErrors().addAll(validationErrors);
		return new ParserResult(null, context);
	}

	protected static XMLInputFactory createXmlInputFactory() {
		XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
		xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, true);
		return xmlInputFactory;
	}
	
	private List validateXML(char[] xmlChars, Schema schema) {
		try (Reader xml = new CharArrayReader(xmlChars)) {
			XmlErrorHandler xmlErrorHandler = new XmlErrorHandler();
			Validator newValidator = schema.newValidator();
			newValidator.setErrorHandler(xmlErrorHandler);
			newValidator.validate(new StreamSource(xml));
			LOGGER.debug("Finished validating xml against schema");

			return xmlErrorHandler.getErrors();
		} catch (SAXException | IOException e) {
			throw new IngestException("Error validating xml against xsd", e);
		}
	}

	private void parse(XMLStreamReader reader, ROMParseHandler rootHandler, Context context) throws XMLStreamException {
		Deque handlerStack = new ArrayDeque<>();
		boolean end = false;
		boolean rootElement = true;

		HandlerStackItem currentHandlers =
				new HandlerStackItem(Collections.singletonList(new HandlerWithPath(rootHandler)), new Path(Collections.emptyList()));
		handlerStack.push(currentHandlers);

		while (!end) {
			reader.next();
			List handlers = currentHandlers.handlers();
			Path fullPath = currentHandlers.fullPath();
			switch (reader.getEventType()) {
			case XMLEvent.START_ELEMENT:
				String qName = reader.getName().getLocalPart();

				if (rootElement && rootHandler instanceof RootHandler) {
					List allowedRootElements = ((RootHandler) rootHandler).getAllowedRootElements();
					if (!allowedRootElements.contains(qName)) {
						context.getXmlValidationErrors().add(String.format("Invalid root XML element [%s], allowed elements %s", qName, allowedRootElements));
						end = true;
						break;
					}
					rootElement = false;
				}

				PathElement element = currentHandlers.addChild(qName, metas(reader));
				Path newPath = fullPath.addElement(element);

				//a new xml element - our current list of handlers either 
				//can't match the new element, 
				//matches the new element fully to give a new handler
				//or partially matches and waits to see if the next element matches

				//only the complex handlers are going to be able to accept this new element
				List complexHandlers = handlers.stream()
																.filter(HandlerWithPath::isComplexHandler)
																.collect(Collectors.toList());

				List newHandlers = complexHandlers.stream()
																   .flatMap(hwp -> hwp.matchingHandlers(element, fullPath).stream())
																   .collect(Collectors.toList());

				complexHandlers.stream()
							   .flatMap(hwp -> hwp.matchingMappingProcessors(fullPath, context.getMappingContext()).stream())
							   .forEach(mapEntryProcessor ->
									   context.getMappingProcessors().put(mapEntryProcessor.getKey(), mapEntryProcessor.getValue()));

				//If there are no handlers then this value cannot be mapped
				if (newHandlers.isEmpty()) {
					LOGGER.debug(newPath + " had no destination");
					context.getMappingContext().getMappings().add(new Mapping(newPath, null, new Path(Collections.emptyList()), null, "had no destination", false, false, false));
				}

				handleAttributes(reader, newPath, newHandlers, context);

				currentHandlers = new HandlerStackItem(newHandlers, newPath);
				handlerStack.push(currentHandlers);
				break;
			case XMLEvent.CHARACTERS:
				String stringVal = reader.getText().trim();
				if (stringVal.length() > 0) {
					context.getMappingContext().getMappings().addAll(HandlerWithPath.createMappings(handlers, stringVal, fullPath));
				}
				break;
			case XMLEvent.END_ELEMENT:
				//step out of the current handler context
				handlerStack.pop();
				if (handlerStack.isEmpty()) {
					//should be no elements left to parse
					end = true;
				} else {
					//remove the exited element from the stacks of the current handlers
					handlerStack.peek().handlers().stream()
								.filter(HandlerWithPath::isComplexHandler)
								.forEach(HandlerWithPath::removeLastIfExists);
					currentHandlers = handlerStack.peek();
				}
				break;
			case XMLEvent.COMMENT:
				// ignore comments
				break;
			case XMLEvent.START_DOCUMENT:
				break;
			case XMLEvent.END_DOCUMENT:
				return;
			default:
				throw new AssertionError("Unhandled XMLEvent value: " + reader.getEventType());
			}
		}
	}

	private Map metas(XMLStreamReader reader) {
		return IntStream.range(0, reader.getAttributeCount())
						.mapToObj(Integer::valueOf)
						.collect(Collectors.toMap(i -> reader.getAttributeName(i).getLocalPart(), i -> reader.getAttributeValue(i)));
	}

	private void handleAttributes(XMLStreamReader reader, Path path, List newHandlers, Context context) {
		for (int i = 0; i < reader.getAttributeCount(); i++) {

			if (reader.getAttributeName(i).getNamespaceURI().equals("http://www.w3.org/2001/XMLSchema-instance")) {
				continue; // Ignore all schema related attributes
			}

			String attributeName = reader.getAttributeName(i).getLocalPart();
			String attributeValue = reader.getAttributeValue(i);

			if (attributeName.equals("fpmlVersion")) {
				continue;//the FpML version is never mapped
			}

			Path aPath = path.addElement(new PathElement(attributeName));

			List complexHandlers = newHandlers.stream()
															   .filter(HandlerWithPath::isComplexHandler)
															   .collect(Collectors.toList());

			List attributeHandlers = new ArrayList<>();
			Map attributeMappingProcessors = new HashMap<>();

			for (HandlerWithPath complexHandler : complexHandlers) {
				// Update path
				complexHandler.addToStackIfNotNull(new PathElement(attributeName));
				// Find all handlers for path
				attributeHandlers.addAll(complexHandler.matchingHandlers(null, path));
				// Find all mapping processors for path
				complexHandler.matchingMappingProcessors(path, context.getMappingContext())
							  .forEach(e -> attributeMappingProcessors.put(e.getKey(), e.getValue()));
			}

			context.getMappingContext().getMappings().addAll(HandlerWithPath.createMappings(attributeHandlers, attributeValue, aPath));

			if (!attributeMappingProcessors.isEmpty()) {
				LOGGER.info(aPath + " has mapping processor");
			}

			attributeMappingProcessors.forEach((modelPath, mapper) -> {
				context.getMappingProcessors().merge(modelPath, mapper, (existingMapper, newMapper) -> {
					// if mapper already exists for model path, append the synonym path to the mapper's synonym paths.
					existingMapper.getSynonymPaths().add(aPath);
					return existingMapper;
				});
			});
			
			
			newHandlers.stream()
					   .filter(HandlerWithPath::isComplexHandler)
					   .forEach(HandlerWithPath::removeLastIfExists);
		}
	}

	static class XmlErrorHandler implements ErrorHandler {

		List errors = new ArrayList<>();

		@Override
		public void warning(SAXParseException e) {
			LOGGER.warn(getParseExceptionInfo(e));
		}

		@Override
		public void error(SAXParseException e) {
			String error = getParseExceptionInfo(e);
			LOGGER.error(getParseExceptionInfo(e));
			errors.add(error);
		}

		@Override
		public void fatalError(SAXParseException e) throws SAXException {
			String message = getParseExceptionInfo(e);
			LOGGER.error("Fatal: " + message);
			throw new SAXException(message);
		}

		private String getParseExceptionInfo(SAXParseException spe) {
			return "URI=" + spe.getSystemId() + " Line=" + spe.getLineNumber() + ": " + spe.getMessage();
		}

		public boolean hasErrors() {
			return !errors.isEmpty();
		}

		public List getErrors() {
			return errors;
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy