code: /******************************************************************************
   *
   *	Copyright (c) 2017-2022, INTO-CPS Association,
   *	c/o Professor Peter Gorm Larsen, Department of Engineering
   *	Finlandsgade 22, 8200 Aarhus N.
   *
   *	This file is part of the INTO-CPS toolchain.
   *
   *	xsd2vdm is free software: you can redistribute it and/or modify
   *	it under the terms of the GNU General Public License as published by
   *	the Free Software Foundation, either version 3 of the License, or
   *	(at your option) any later version.
   *
   *	xsd2vdm is distributed in the hope that it will be useful,
   *	but WITHOUT ANY WARRANTY; without even the implied warranty of
   *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *	GNU General Public License for more details.
   *
   *	You should have received a copy of the GNU General Public License
   *	along with xsd2vdm. If not, see <http://www.gnu.org/licenses/>.
   *	SPDX-License-Identifier: GPL-3.0-or-later
   *
   ******************************************************************************/
  
  package xsd2vdm;
  
  import java.util.Map;
  import java.util.Stack;
  import org.xml.sax.Attributes;
  import org.xml.sax.Locator;
  import org.xml.sax.SAXException;
  import org.xml.sax.helpers.DefaultHandler;
  
  import types.Field;
  import types.RecordType;
  import types.Type;
  import values.AnyValue;
  import values.RecordValue;
  import values.SimpleValue;
  import values.VDMValue;
  
  public class XMLSaxHandler extends DefaultHandler
  {
  	private final Map<String, Type> schema;
  	private final Stack<VDMValue> stack = new Stack<VDMValue>();
  	private Locator locator = null;
  	private VDMValue finalValue = null;
  
  	public XMLSaxHandler(Map<String, Type> schema)
  	{
  		this.schema = schema;
  	}
  
  	@Override
  	public void setDocumentLocator(Locator locator)
  	{
  		this.locator = locator;
  	}
  
  	@Override
  	public void startElement(String uri, String localName, String qName, Attributes attributes)
  	{
  		String mapped = map(qName);
  		
  		if (mapped != null)
  		{
  			qName = mapped;
  		}
  		
  		if (schema.containsKey(qName))
  		{
  			RecordType recordType = (RecordType) schema.get(qName);
  			RecordValue recordValue = new RecordValue(recordType, locator);
  			
  			for (int i=0; i<attributes.getLength(); i++)
  			{
  				String aname = attributes.getQName(i);
  				String avalue = attributes.getValue(i);
  				
  				if (!aname.startsWith("xmlns:"))	// Ignore embedded xmlns?
  				{
  					Field field = recordType.getField(aname);
  
  					if (field == null)
  					{
  						aname = "any";
  						field = recordType.getField(aname);
  					}
  					
  					if (field != null)
  					{
  						Type atype = field.getFieldType();
  						VDMValue vdmValue = atype.valueOf(avalue, locator);
  						
  						if (!recordValue.setAttribute(aname, vdmValue))
  						{
  							dumpStack("Attribute not set: " + aname, recordValue);
  						}
  					}
  					else
  					{
  						dumpStack("Field not found: " + qName + "." + aname, recordValue);
  					}
  				}
  			}
  
  			stack.push(recordValue);
  		}
  		else if (!stack.isEmpty() && stack.peek().hasAny())
  		{
  			stack.push(new AnyValue(qName, attributes, locator));
  		}
  		else
  		{
  			dumpStack("VDM schema does not contain type qName " + qName, null);
  		}
  	}
  	
  	@Override
  	public void characters(char[] ch, int start, int length) throws SAXException
  	{
  		String value = new String(ch, start, length).trim();
  		
  		if (!value.isEmpty())
  		{
  			if (!stack.isEmpty() && stack.peek() instanceof SimpleValue)
  			{
  				SimpleValue simple = (SimpleValue)stack.peek();
  				simple.setValue(value);
  			}
  			else
  			{
  				dumpStack("Cannot add character content to " + stack.peek().getType(), null);
  			}
  		}
  	}
  
  	@Override
  	public void endElement(String uri, String localName, String qName)
  	{
  		VDMValue value = stack.pop();
  
  		if (!stack.isEmpty())
  		{
  			if (stack.peek() instanceof RecordValue)
  			{
  				RecordValue recordValue = (RecordValue)stack.peek();
  				
  				if (!recordValue.setField(qName, value))
  				{
  					dumpStack("Cannot add sub-element: " + qName + " to " + stack.peek().getType(), recordValue);
  				}
  			}
  			else if (stack.peek() instanceof AnyValue)
  			{
  				AnyValue any = (AnyValue)stack.peek();
  				any.setField(qName, value);
  			}
  			else
  			{
  				dumpStack("Cannot add sub-element " + value + " to " + stack.peek().getType(), null);
  			}
  		}
  		else
  		{
  			finalValue = value;
  		}
  	}
  
  	public VDMValue getVDMValue()
  	{
  		return finalValue;
  	}
  
  	private void dumpStack(String message, VDMValue topValue)
  	{
  		System.err.println(message);
  		String indent = "";
  		
  		for (VDMValue v: stack)
  		{
  			System.err.println(indent + v.getType().signature());
  			indent = indent + " ";
  		}
  		
  		if (topValue != null)
  		{
  			System.err.println(indent + topValue.getType().signature());
  		}
  		
  		System.exit(1);
  	}
  	
  	private String map(String qName)
  	{
  		StringBuilder path = new StringBuilder();
  		String sep = "";
  		
  		for (VDMValue v: stack)
  		{
  			if (v instanceof RecordValue)
  			{
  				RecordValue rv = (RecordValue)v;
  				path.append(sep);
  				path.append(rv.getType().signature());
  				sep = ".";
  			}
  		}
  		
  		path.append(sep);
  		path.append(qName);
  
  		return Xsd2VDM.getProperty(path.toString());
  	}
  }
  