/******************************************************************************* * Copyright (c) 2011 Contributors. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Eclipse Public License v 2.0 * which accompanies this distribution and is available at * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt * * Contributors: * Abraham Nevado - Lucierna initial implementation *******************************************************************************/ package org.aspectj.weaver.loadtime.definition; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class LightXMLParser { private final static char NULL_CHAR = '\0'; private Map attributes; private ArrayList children; private String name; private char pushedBackChar; private Reader reader; private static Map entities = new HashMap<>(); static { entities.put("amp", new char[] { '&' }); entities.put("quot", new char[] { '"' }); entities.put("apos", new char[] { '\'' }); entities.put("lt", new char[] { '<' }); entities.put("gt", new char[] { '>' }); } public LightXMLParser() { this.name = null; this.attributes = new HashMap<>(); this.children = new ArrayList<>(); } public ArrayList getChildrens() { return this.children; } public String getName() { return this.name; } public void parseFromReader(Reader reader) throws Exception { this.pushedBackChar = NULL_CHAR; this.attributes = new HashMap<>(); this.name = null; this.children = new ArrayList<>(); this.reader = reader; while (true) { // Skips whiteSpaces, blanks, \r\n.. char c = this.skipBlanks(); // All xml should start by 'z')) && ((c > 'Z') || (c < 'A')) && ((c > '9') || (c < '0')) && (c != '_') && (c != '-') && (c != '.') && (c != ':')) { this.pushBackChar(c); return; } result.append(c); } } private void getString(StringBuffer string) throws Exception { char delimiter = this.getNextChar(); if ((delimiter != '\'') && (delimiter != '"')) { throw new Exception("Parsing error. Expected ' or \" but got: " + delimiter); } while (true) { char c = this.getNextChar(); if (c == delimiter) { return; } else if (c == '&') { this.mapEntity(string); } else { string.append(c); } } } private void getPCData(StringBuffer data) throws Exception { while (true) { char c = this.getNextChar(); if (c == '<') { c = this.getNextChar(); if (c == '!') { this.checkCDATA(data); } else { this.pushBackChar(c); return; } } else { data.append(c); } } } private boolean checkCDATA(StringBuffer buf) throws Exception { char c = this.getNextChar(); if (c != '[') { this.pushBackChar(c); this.skipCommentOrXmlTag(0); return false; } else if (!this.checkLiteral("CDATA[")) { this.skipCommentOrXmlTag(1); // one [ has already been read return false; } else { int delimiterCharsSkipped = 0; while (delimiterCharsSkipped < 3) { c = this.getNextChar(); switch (c) { case ']': if (delimiterCharsSkipped < 2) { delimiterCharsSkipped++; } else { buf.append(']'); buf.append(']'); delimiterCharsSkipped = 0; } break; case '>': if (delimiterCharsSkipped < 2) { for (int i = 0; i < delimiterCharsSkipped; i++) { buf.append(']'); } delimiterCharsSkipped = 0; buf.append('>'); } else { delimiterCharsSkipped = 3; } break; default: for (int i = 0; i < delimiterCharsSkipped; i++) { buf.append(']'); } buf.append(c); delimiterCharsSkipped = 0; } } return true; } } private void skipCommentOrXmlTag(int bracketLevel) throws Exception { char delim = NULL_CHAR; int level = 1; char c; if (bracketLevel == 0) { c = this.getNextChar(); if (c == '-') { c = this.getNextChar(); if (c == ']') { bracketLevel--; } else if (c == '[') { bracketLevel++; } else if (c == '-') { this.skipComment(); return; } } else if (c == '[') { bracketLevel++; } } while (level > 0) { c = this.getNextChar(); if (delim == NULL_CHAR) { if ((c == '"') || (c == '\'')) { delim = c; } else if (bracketLevel <= 0) { if (c == '<') { level++; } else if (c == '>') { level--; } } if (c == '[') { bracketLevel++; } else if (c == ']') { bracketLevel--; } } else { if (c == delim) { delim = NULL_CHAR; } } } } private void parseNode(LightXMLParser elt) throws Exception { // Now we are in a new node element. Get its name StringBuffer buf = new StringBuffer(); this.getNodeName(buf); String name = buf.toString(); elt.setName(name); char c = this.skipBlanks(); while ((c != '>') && (c != '/')) { // Get attributes emptyBuf(buf); this.pushBackChar(c); this.getNodeName(buf); String key = buf.toString(); c = this.skipBlanks(); if (c != '=') { throw new Exception("Parsing error. Expected = but got: " + c); } // Go up to " character and push it back this.pushBackChar(this.skipBlanks()); emptyBuf(buf); this.getString(buf); elt.setAttribute(key, buf); // Skip blanks c = this.skipBlanks(); } if (c == '/') { c = this.getNextChar(); if (c != '>') { throw new Exception("Parsing error. Expected > but got: " + c); } return; } // Now see if we got content, or CDATA, if content get it: it is free... emptyBuf(buf); c = this.getWhitespaces(buf); if (c != '<') { // It is PCDATA this.pushBackChar(c); this.getPCData(buf); } else { // It is content: get it, or CDATA. while (true) { c = this.getNextChar(); if (c == '!') { if (this.checkCDATA(buf)) { this.getPCData(buf); break; } else { c = this.getWhitespaces(buf); if (c != '<') { this.pushBackChar(c); this.getPCData(buf); break; } } } else { if (c != '/') { emptyBuf(buf); } if (c == '/') { this.pushBackChar(c); } break; } } } if (buf.length() == 0) { // It is a comment while (c != '/') { if (c == '!') { for (int i = 0; i < 2; i++) { c = this.getNextChar(); if (c != '-') { throw new Exception("Parsing error. Expected element or comment"); } } this.skipComment(); } else { // it is a new node this.pushBackChar(c); LightXMLParser child = this.createAnotherElement(); this.parseNode(child); elt.addChild(child); } c = this.skipBlanks(); if (c != '<') { throw new Exception("Parsing error. Expected <, but got: " + c); } c = this.getNextChar(); } this.pushBackChar(c); } // Here content could be grabbed c = this.getNextChar(); if (c != '/') { throw new Exception("Parsing error. Expected /, but got: " + c); } this.pushBackChar(this.skipBlanks()); if (!this.checkLiteral(name)) { throw new Exception("Parsing error. Expected " + name); } if (this.skipBlanks() != '>') { throw new Exception("Parsing error. Expected >, but got: " + c); } } private void skipComment() throws Exception { int dashes = 2; while (dashes > 0) { char ch = this.getNextChar(); if (ch == '-') { dashes -= 1; } else { dashes = 2; } } char nextChar = this.getNextChar(); if (nextChar != '>') { throw new Exception("Parsing error. Expected > but got: " + nextChar); } } private boolean checkLiteral(String literal) throws Exception { int length = literal.length(); for (int i = 0; i < length; i++) { if (this.getNextChar() != literal.charAt(i)) { return false; } } return true; } private char getNextChar() throws Exception { if (this.pushedBackChar != NULL_CHAR) { char c = this.pushedBackChar; this.pushedBackChar = NULL_CHAR; return c; } else { int i = this.reader.read(); if (i < 0) { throw new Exception("Parsing error. Unexpected end of data"); } else { return (char) i; } } } private void mapEntity(StringBuffer buf) throws Exception { char c = this.NULL_CHAR; StringBuilder keyBuf = new StringBuilder(); while (true) { c = this.getNextChar(); if (c == ';') { break; } keyBuf.append(c); } String key = keyBuf.toString(); if (key.charAt(0) == '#') { try { if (key.charAt(1) == 'x') { c = (char) Integer.parseInt(key.substring(2), 16); } else { c = (char) Integer.parseInt(key.substring(1), 10); } } catch (NumberFormatException e) { throw new Exception("Unknown entity: " + key); } buf.append(c); } else { char[] value = entities.get(key); if (value == null) { throw new Exception("Unknown entity: " + key); } buf.append(value); } } private void pushBackChar(char c) { this.pushedBackChar = c; } private void addChild(LightXMLParser child) { this.children.add(child); } private void setAttribute(String name, Object value) { this.attributes.put(name, value.toString()); } public Map getAttributes() { return this.attributes; } private LightXMLParser createAnotherElement() { return new LightXMLParser(); } private void setName(String name) { this.name = name; } private void emptyBuf(StringBuffer buf) { buf.setLength(0); } }