/* * SyntaxParser.java * * Copyright (C) 2015 Pixelgaffer * * This work is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2 of the License, or any later * version. * * This work 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 version 2 and version 3 of the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.pixelgaffer.katepartparser; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.fxmisc.richtext.StyleSpans; import org.fxmisc.richtext.StyleSpansBuilder; import org.pixelgaffer.katepartparser.context.Context; import org.pixelgaffer.katepartparser.context.ContextRule; import org.pixelgaffer.katepartparser.context.RulesFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import lombok.Getter; import lombok.ToString; @ToString(exclude = { "lists", "contexts" }) public class SyntaxParser { @Getter private File file; @Getter private Style style; // required attributes of the root element @Getter private String name; @Getter private String section; @Getter private Set<String> extensions; // optional attributes of the root element @Getter private String mimetype; @Getter private String version; @Getter private String kateversion; @Getter private int priority; @Getter private String author; @Getter private String license; @Getter private boolean hidden; // all lists inside the highlighting element @Getter private Map<String, List<String>> lists = new HashMap<>(); // all contexts @Getter private Map<String, Context> contexts = new HashMap<>(); // der default context private Context defaultContext; // all item datas @Getter private final Map<String, NamedStyleEntry> itemDatas = new HashMap<>(); public SyntaxParser (String filename, SyntaxFileResolver resolver, Style s) throws ParserConfigurationException, IOException, SAXException { this(filename == null ? null : new File(filename), resolver, s); } public SyntaxParser (File syntaxFile, SyntaxFileResolver resolver, Style s) throws ParserConfigurationException, IOException, SAXException { file = syntaxFile; style = s; DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setValidating(false); documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document doc = documentBuilder.parse(file); Element rootElement = doc.getDocumentElement(); name = rootElement.getAttribute("name"); section = rootElement.getAttribute("section"); extensions = new HashSet<>(Arrays.asList(rootElement.getAttribute("extensions").split(";"))); mimetype = rootElement.getAttribute("mimetype"); version = rootElement.getAttribute("version"); kateversion = rootElement.getAttribute("kateversion"); hidden = Boolean.parseBoolean(rootElement.getAttribute("hidden")); try { priority = Integer.parseInt(rootElement.getAttribute("priority")); } catch (NumberFormatException nfe) { } author = rootElement.getAttribute("author"); license = rootElement.getAttribute("license"); NodeList nodeList = rootElement.getChildNodes(); if (nodeList != null) { for (int i = 0; i < nodeList.getLength(); i++) { Node n = nodeList.item(i); if (n instanceof Element) { Element e = (Element)n; if (e.getTagName().equals("highlighting")) parseHighlighting(e, resolver); // else // System.err.println("Unbekanntes Element: " + e); } } } } private void parseHighlighting (Element highlighting, SyntaxFileResolver resolver) throws ParserConfigurationException, IOException, SAXException { NodeList nodeList = highlighting.getChildNodes(); if (nodeList != null) { for (int i = 0; i < nodeList.getLength(); i++) { Node n = nodeList.item(i); if (n instanceof Element) { Element e = (Element)n; // ###Liste############################################################################## if (e.getTagName().equals("list")) { List<String> list = new ArrayList<>(); NodeList nodeList1 = e.getElementsByTagName("item"); if (nodeList1 != null) { for (int j = 0; j < nodeList1.getLength(); j++) { Node n1 = nodeList1.item(j); if (n1 instanceof Element) { Element e1 = (Element)n1; list.add(e1.getTextContent().trim()); } } } lists.put(e.getAttribute("name"), list); } // ###Context############################################################################ else if (e.getTagName().equals("contexts")) { NodeList contexts = e.getChildNodes(); if (contexts != null) { for (int j = 0; j < contexts.getLength(); j++) { Node n1 = contexts.item(j); if (n1 instanceof Element) { Context context = parseContext((Element)n1, resolver); this.contexts.put(context.getName(), context); } } } } // ###ItemData########################################################################### else if (e.getTagName().equals("itemDatas")) { NodeList itemDatas = e.getChildNodes(); if (itemDatas != null) { for (int j = 0; j < itemDatas.getLength(); j++) { Node n1 = itemDatas.item(j); if (n1 instanceof Element) { Element itemData = (Element)n1; if (!itemData.getTagName().equals("itemData")) { System.err .println("Unbekanntes Element in " + file.getName() + ": " + itemData); continue; } try { String name = itemData.getAttribute("name"); StyleEntry parent = style.getEntry(itemData.getAttribute("defStyleNum")); NamedStyleEntry styleEntry; if (parent == null) { System.err.println("Undefinierter Standart-Stil: " + itemData.getAttribute("defStyleNum")); styleEntry = new NamedStyleEntry(); } else styleEntry = new NamedStyleEntry(parent); styleEntry.setName("itemData" + j); if (itemData.hasAttribute("color")) styleEntry.setColor(itemData.getAttribute("color")); this.itemDatas.put(name, styleEntry); } catch (NoSuchMethodException nsme) { System.err.println("Unbekannter Standart-Stil in " + file.getName() + ": " + nsme.getMessage()); } catch (SecurityException se) { se.printStackTrace(); } catch (ReflectiveOperationException roe) { roe.printStackTrace(); } } } } } else { System.err.println("Unbekanntes Element in " + file.getName() + ": " + e); } } } } } private Context parseContext (Element context, SyntaxFileResolver resolver) throws ParserConfigurationException, IOException, SAXException { Context c = new Context(context.getAttribute("name"), context.getAttribute("attribute"), context.getAttribute("lineEndContext")); if (context.getAttribute("fallthrough") != null) { c.setFallthrough(Boolean.parseBoolean(context.getAttribute("fallthrough"))); c.setFallthroughContext(context.getAttribute("fallthroughContext")); } if (defaultContext == null) defaultContext = c; NodeList nodeList = context.getChildNodes(); if (nodeList != null) { for (int i = 0; i < nodeList.getLength(); i++) { Node n = nodeList.item(i); if (n instanceof Element) { Element e = (Element)n; if (e.getTagName().equals("IncludeRules")) { String contextName = e.getAttribute("context"); Context toInclude = null; if (contextName.startsWith("##")) { SyntaxParser other = new SyntaxParser( resolver.getSyntaxFile(contextName.substring(2)), resolver, getStyle()); for (String list : other.getLists().keySet()) { if (lists.containsKey(list)) lists.get(list).addAll(other.getLists().get(list)); else lists.put(list, other.getLists().get(list)); } for (String itemData : other.getItemDatas().keySet()) if (!itemDatas.containsKey(itemData)) itemDatas.put(itemData, other.getItemDatas().get(itemData)); for (String ct : other.getContexts().keySet()) { if (!contexts.containsKey(ct)) contexts.put(ct, other.getContexts().get(ct)); // else // System.err.println( // "Duplicate Context " + ct + ", ignoring imported Context from " + other.getName()); } toInclude = other.getContexts().get(c.getName()); if (toInclude == null) toInclude = other.defaultContext; } else toInclude = contexts.get(contextName); if (toInclude == null) System.err.println("Unbekannter Context in " + file.getName() + ": " + e.getAttribute("context")); else c.getRules().addAll(toInclude.getRules()); } else { try { ContextRule rule = RulesFactory.parseRule(e); c.getRules().add(rule); } catch (ClassNotFoundException cnfe) { System.err.println("Unbekannte Regel in " + file.getName() + ": " + cnfe.getMessage()); } catch (SecurityException se) { se.printStackTrace(); } catch (ReflectiveOperationException roe) { roe.printStackTrace(); } } } } } return c; } public URL generateStylesheet (String id) throws IOException { File tmp = File.createTempFile("stylesheet", ".css"); tmp.deleteOnExit(); PrintWriter out = new PrintWriter(tmp); for (NamedStyleEntry entry : itemDatas.values()) out.println(entry.toCss(false)); out.println("#" + id + "{-fx-background-color:" + style.getNormal().getBgColor() + ";" + "-fx-text-fill:" + style.getNormal().getColor() + ";" + "-fx-prompt-text-fill:" + style.getNormal().getColor() + "}"); out.close(); return tmp.toURI().toURL(); } public StyleSpans<Collection<String>> computeHighlighting (String text) { StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>(); Deque<Context> context = new LinkedList<>(); context.offer(defaultContext); Deque<String> lines = new LinkedList<>(Arrays.asList(text.split("\n"))); String line = lines.pollFirst(); while (line != null) { int pos = 0; lineloop: while (line.length() > pos) { if (context.isEmpty()) { System.err.println("Fehler in " + file.getName() + ": Die Context-Liste ist leer"); context.offer(defaultContext); } for (ContextRule rule : context.getFirst().getRules()) { int chars = rule.matches(line, pos, lists); if (chars > 0) { String attribute = rule.getAttribute(); if (attribute == null) attribute = context.getFirst().getAttribute(); spansBuilder.add(Collections.singleton(itemDatas.get(attribute).getName()), chars); pos += chars; String c = rule.getContext(); while (c.startsWith("#pop")) { c = c.substring(4); if (context.size() > 1) context.pollFirst(); else System.err.println("#pop für oberstes Element in " + file.getName()); } if (!c.isEmpty() && !c.equals("#stay")) { if (contexts.containsKey(c)) context.addFirst(contexts.get(c)); else System.err .println("Unbekannter Context in " + file.getName() + " (referenziert von " + rule + ")"); } continue lineloop; } } String attribute = defaultContext.getAttribute(); if (itemDatas.containsKey(attribute)) spansBuilder.add(Collections.singleton(itemDatas.get(attribute).getName()), 1); else { System.err.println("Unbekanntes Attribut in " + file.getName() + ": " + attribute); spansBuilder.add(Collections.emptyList(), 1); } pos++; } line = lines.pollFirst(); if (line != null) spansBuilder.add(Collections.emptyList(), 1); String c = context.getFirst().getLineEndContext(); while (c.startsWith("#pop")) { c = c.substring(4); if (context.size() > 1) context.pollFirst(); else System.err.println("#pop für oberstes Element in " + file.getName()); } if (!c.isEmpty() && !c.equals("#stay")) { if (contexts.containsKey(c)) context.addFirst(contexts.get(c)); else System.err.println("Unbekannter Context in " + file.getName() + " (referenziert von " + context.getFirst().getName() + ")"); } } return spansBuilder.create(); } }