/******************************************************************************* * Copyright (c) 2015 ARM Ltd. and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * ARM Ltd and ARM Germany GmbH - Initial API and implementation *******************************************************************************/ package com.arm.cmsis.pack.parser; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.arm.cmsis.pack.CpStrings; import com.arm.cmsis.pack.common.CmsisConstants; import com.arm.cmsis.pack.data.ICpItem; import com.arm.cmsis.pack.data.ICpRootItem; /** * Base class to parse CMSIS pack-related files */ public abstract class CpXmlParser implements ICpXmlParser { protected ICpItem rootItem = null; // represents top-level item being constructed protected String xmlFile = null; // current XML file protected String xmlString = null; // current XML string protected String xsdFile = null; // schema file with absolute path protected Set<String> ignoreTags = null; // tags to ignore (partly parsed file) protected Set<String> ignoreWriteTags = null; // tags to ignore when writing to xml file // errors for current file protected List<String> errorStrings = new LinkedList<String>(); protected int nErrors = 0; protected int nWarnings = 0; // DOM private DocumentBuilderFactory docBuilderFactory = null; private DocumentBuilder docBuilder = null; protected XmlErrorHandler errorHandler = null; public CpXmlParser() { } public CpXmlParser(String xsdFile) { this.xsdFile = xsdFile; } @Override public void clear() { xmlFile = null; xmlString = null; rootItem = null; errorStrings.clear(); nErrors = 0; nWarnings = 0; if (docBuilder != null) { docBuilder.reset(); } } @Override public String getXsdFile() { return xsdFile; } @Override public void setXsdFile(String xsdFile) { this.xsdFile = xsdFile; } /** * @return the xmlFile */ public String getXmlFile() { return xmlFile; } @Override public List<String> getErrorStrings() { return errorStrings; } @Override public int getErrorCount() { return nErrors; } @Override public int getWarningCount() { return nWarnings; } @Override public void setIgnoreTags(Set<String> ignoreTags) { this.ignoreTags = ignoreTags; } @Override public void setWriteIgnoreTags(Set<String> ignoreTags) { ignoreWriteTags = ignoreTags; } /** * Check if an item with given tag should be ignored by reading or writing * @param tag tag to check * @return true if tag is ignored */ protected boolean isTagIgnored(String tag) { if(tag == null || tag.isEmpty()) { return true; } if (ignoreTags == null || ignoreTags.isEmpty()) { return false; } return ignoreTags.contains(tag); } /** * Check if item should be ignored by writing * @param item {@link ICpItem} to check * @return true if ignored */ protected boolean isItemIgnored(ICpItem item) { if(item == null) { return true; } return isTagIgnored(item.getTag()); } @Override public ICpItem createItem(ICpItem parent, String tag) { if(parent != null) { return parent.createItem(parent, tag); } if(rootItem == null) { rootItem = createRootItem(tag) ; } return rootItem; } protected class XmlErrorHandler implements ErrorHandler { public XmlErrorHandler() { // TODO Auto-generated constructor stub } @Override public void error(SAXParseException arg0) throws SAXException { addErrorString(arg0, CpStrings.CpXmlParser_Error); nErrors++; } @Override public void fatalError(SAXParseException arg0) throws SAXException { addErrorString(arg0, CpStrings.CpXmlParser_FatalError); nErrors++; throw arg0; } @Override public void warning(SAXParseException arg0) throws SAXException { addErrorString(arg0, CpStrings.CpXmlParser_Warning); nWarnings++; } protected void addErrorString(SAXParseException arg0, final String severity) { String err = xmlFile; int line = arg0.getLineNumber(); int col = arg0.getColumnNumber(); if (line > 0) { err += "("; //$NON-NLS-1$ err += line; err += ","; //$NON-NLS-1$ err += col; err += ")"; //$NON-NLS-1$ } err += ": " + severity + ": "; //$NON-NLS-1$ //$NON-NLS-2$ err += arg0.getLocalizedMessage(); errorStrings.add(err); } } @Override public boolean init() { try { if(docBuilderFactory == null) { docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setValidating(false); docBuilderFactory.setNamespaceAware(true); if (xsdFile != null && !xsdFile.isEmpty()) { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = schemaFactory.newSchema(new Source[] {new StreamSource(xsdFile)}); docBuilderFactory.setSchema(schema); } } if(docBuilder == null){ docBuilder = docBuilderFactory.newDocumentBuilder(); errorHandler = new XmlErrorHandler(); } docBuilder.setErrorHandler(errorHandler); } catch (ParserConfigurationException e) { String err = CpStrings.CpXmlParser_ErrorParserInit; err += ": "; //$NON-NLS-1$ err += e.toString(); errorStrings.add(err); nErrors++; docBuilder = null; e.printStackTrace(); return false; } catch (SAXException e) { String err = CpStrings.CpXmlParser_ErrorSchemaInit; err += " " + xsdFile; //$NON-NLS-1$ err += ": "; //$NON-NLS-1$ err += e.toString(); errorStrings.add(err); nErrors++; docBuilder = null; e.printStackTrace(); } return true; } @Override public ICpItem parseXmlString(String xml) { clear(); this.xmlString = xml; if(!init()) { return null; } Document domDoc = null; StringReader sr = new StringReader(xml); InputSource is = new InputSource(sr); try { domDoc = docBuilder.parse(is); } catch (SAXException | IOException e ) { String err = CpStrings.CpXmlParser_ErrorParsingFile; err += " " + xmlFile; //$NON-NLS-1$ err += "': "; //$NON-NLS-1$ err += e.toString(); errorStrings.add(err); nErrors++; e.printStackTrace(); } finally { sr.close(); } if(domDoc == null) { return null; } Element domElement = domDoc.getDocumentElement(); if (domElement == null) { return null; } if(parseElement(domElement, null)) { return rootItem; } return null; } @Override public ICpItem parseFile(String file) { clear(); this.xmlFile = file; if(!init()) { return null; } Document domDoc = null; InputStream sr = null; try { sr = new FileInputStream(xmlFile); domDoc = docBuilder.parse(sr); } catch (SAXException | IOException e ) { String err = CpStrings.CpXmlParser_ErrorParsingFile; err += " " + xmlFile; //$NON-NLS-1$ err += "': "; //$NON-NLS-1$ err += e.toString(); errorStrings.add(err); nErrors++; //e.printStackTrace(); } if(sr != null) { try { sr.close(); } catch (Exception e) { e.printStackTrace(); } } sr = null; if(domDoc == null) { return null; } Element domElement = domDoc.getDocumentElement(); if (domElement == null) { return null; } if(parseElement(domElement, null)) { return rootItem; } return null; } /** * Parses single element node, creates child ICpItem and adds it to parent * @param elementNode node to parse * @param parent parent ICpItem * @return true if successful */ protected boolean parseElement(Node elementNode, ICpItem parent) { // set element tag name String tag = elementNode.getNodeName(); if (isTagIgnored(tag)) { return true; // no further processing } ICpItem item = createItem(parent, tag); if(item == null) { return false; } // process node attributes NamedNodeMap attributes = elementNode.getAttributes(); if (attributes != null && attributes.getLength() > 0) { for (int i = 0; i < attributes.getLength(); i++) { Node node = attributes.item(i); if (node != null) { String key = node.getNodeName(); if (key == null) { continue; } key = key.trim(); if (key.isEmpty()) { continue; } String value = node.getNodeValue(); if (value == null) { continue; } value = adjustAttributeValue(key, value.trim()); item.attributes().setAttribute(key, value); } } } // add child item here since parent implementation can query item attributes if(parent != null) { parent.addChild(item); } // insert children and text for (Node node = elementNode.getFirstChild(); node != null; node = node.getNextSibling()) { switch (node.getNodeType()) { case Node.ELEMENT_NODE: { if (!parseElement(node, item)) { return false; } break; } case Node.TEXT_NODE: { String text = node.getNodeValue(); if (text != null) { item.setText(text.trim()); } break; } default: break; } } // do some post processing of the item processItem(item); return true; } /** * Process the item just created * @param item item just created */ protected void processItem(ICpItem item) { } @Override public String adjustAttributeValue(String key, String value) { if(key.equals(CmsisConstants.DFPU)) { switch(value){ case "1": //$NON-NLS-1$ case "FPU": //$NON-NLS-1$ return CmsisConstants.SP_FPU; case "0": //$NON-NLS-1$ return CmsisConstants.NO_FPU; default: return value; } } else if(key.equals(CmsisConstants.DMPU)){ switch(value){ case "1": //$NON-NLS-1$ return CmsisConstants.MPU; case "0": //$NON-NLS-1$ return CmsisConstants.NO_MPU; default: return value; } } else if(key.equals(CmsisConstants.DDSP)){ switch(value){ case "1": //$NON-NLS-1$ return CmsisConstants.DSP; case "0": //$NON-NLS-1$ return CmsisConstants.NO_DSP; default: return value; } } else if(key.equals(CmsisConstants.DTZ)){ switch(value){ case "1": //$NON-NLS-1$ return CmsisConstants.TZ; case "0": //$NON-NLS-1$ return CmsisConstants.NO_TZ; default: return value; } } else if(key.equals(CmsisConstants.DSECURE)){ switch(value){ case "1": //$NON-NLS-1$ return CmsisConstants.SECURE; case "0": //$NON-NLS-1$ return CmsisConstants.NON_SECURE; default: return value; } } // convert boolean values to 1 for consistency if(value.equals("true")) { //$NON-NLS-1$ return "1"; //$NON-NLS-1$ } else if(value.equals("false")) //$NON-NLS-1$ { return "0"; //$NON-NLS-1$ } if(value.startsWith("\\\\")) { //$NON-NLS-1$ return value; } if(value.indexOf(':') == 1) { return value; } return value.replace('\\', '/'); // convert all backslashes to slashes for consistency } @Override public boolean writeToXmlFile(ICpItem root, String file) { String xml = writeToXmlString(root); if(xml == null) { return false; } this.xmlFile = file; try { File outputFile = new File(file); FileWriter fw = new FileWriter(outputFile); fw.write(xml); fw.close(); if(root instanceof ICpRootItem) { ((ICpRootItem)root).setFileName(file); } } catch (IOException e) { errorStrings.add("Error writting to '" + file + "' file:"); //$NON-NLS-1$ //$NON-NLS-2$ errorStrings.add(e.toString()); e.printStackTrace(); return false; } return true; } @Override public String writeToXmlString(ICpItem root) { String xml = null; clear(); this.xmlFile = null; if(!init()) { return xml; } Document domDoc = createDomDocument(root); if(domDoc == null) { return xml; } try { Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ DOMSource source = new DOMSource(domDoc); StringWriter buffer = new StringWriter(); StreamResult dest = new StreamResult(buffer); transformer.transform(source, dest); buffer.close(); xml = buffer.toString(); } catch ( TransformerFactoryConfigurationError | TransformerException | IOException e) { String error = CpStrings.CpXmlParser_ErrorCreatingXML; error += ": "; //$NON-NLS-1$ error += e.toString(); errorStrings.add(error); e.printStackTrace(); return null; } return xml; } /** * Creates DOM document * @param root root IcpItem to create document for * @return creates DOM */ protected Document createDomDocument(ICpItem root){ Document domDoc = docBuilder.newDocument(); createElement(domDoc, null, root); return domDoc; } /** * Recursively creates DOM element node for supplied ICpItem * @param doc DOM document owner of the element * @param parentNode parent DOM node for the element * @param item ICpItem to process * @return created element node */ protected Node createElement(Document doc, Node parentNode, ICpItem item){ if(isItemIgnored(item)) { return null; } Element node = null; node = doc.createElement(item.getTag()); if(parentNode != null) { // node = doc.createElement(item.getTag()); parentNode.appendChild(node); } else { doc.appendChild(node); node.setAttribute("xmlns:xs", "http://www.w3.org/2001/XMLSchema-instance"); //$NON-NLS-1$ //$NON-NLS-2$ if (xsdFile != null && !xsdFile.isEmpty()) { File f = new File(xsdFile); String xsdName = f.getName(); node.setAttribute("xs:noNamespaceSchemaLocation", xsdName); //$NON-NLS-1$ } } if(item.attributes().hasAttributes()) { Map<String, String> attributesMap = item.attributes().getAttributesAsMap(); for(Entry<String, String> e: attributesMap.entrySet()) { node.setAttribute(e.getKey(), e.getValue()); } } String text = item.getText(); if(!text.isEmpty()) { node.appendChild(doc.createTextNode(text)); } else if(item.hasChildren()){ Collection<? extends ICpItem> children = item.getChildren(); for(ICpItem child : children) { createElement(doc, node, child); } } return node; } }