package com.github.dtschust.zork.parser; import com.github.dtschust.zork.*; import com.github.dtschust.zork.types.*; import org.w3c.dom.CharacterData; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; import java.util.*; public class ZorkReader { private final String filename; public ZorkReader(String filename) { this.filename = filename; } /* Get a string from an element (XML parsing stuff)*/ public static String getString(Element e) { Node child = e.getFirstChild(); if (child instanceof CharacterData) { CharacterData cd = (CharacterData) child; return cd.getData(); } return "?"; } private static String getItemOrDefault(Element element, String name, String base) { NodeList field = element.getElementsByTagName(name); return (field.getLength() > 0) ? getString((Element) field.item(0)) : base; } private static void addCreature(ZorkGame data, Element element) { final Set vulnerabilities = new HashSet<>(); final List conditions = new ArrayList<>(); final List prints = new ArrayList<>(); final List actions = new ArrayList<>(); NodeList attacks = element.getElementsByTagName("attack"); for (Element attack : iterElements(attacks)) { readConditionsInTrigger(attack, conditions); for (Element print : iterElements(attack.getElementsByTagName("print"))) { prints.add(getString(print)); } for (Element action : iterElements(attack.getElementsByTagName("action"))) { actions.add(getString(action)); } } for (Element vuln : iterElements(element.getElementsByTagName("vulnerability"))) { vulnerabilities.add(getString(vuln)); } /* Get all possible creature attributes */ ZorkCreature tempCreature = new ZorkCreature( getItemOrDefault(element, "name", ""), getItemOrDefault(element, "description", ""), vulnerabilities, conditions, prints, actions ); readTriggersInObject(element, tempCreature); tempCreature.updateStatus(getItemOrDefault(element, "status", "")); /* Put each creature in the creatures hashmap, the generic object hashmap, and the objectlookup hashmap*/ data.addObjectThroughLookup("creature", tempCreature); } private static void addContainer(ZorkGame data, Element element) { /*Get all possible container attributes*/ ZorkContainer tempCont = new ZorkContainer( getItemOrDefault(element, "name", ""), getItemOrDefault(element, "description", "") ); readTriggersInObject(element, tempCont); tempCont.updateStatus(getItemOrDefault(element, "status", "")); /*Initially assume a closed container*/ for (Element accept : iterElements(element.getElementsByTagName("accept"))) { /* If container has an accepts attribute, then it is always open*/ tempCont.open(); tempCont.accept.add(getString(accept)); } for (Element item : iterElements(element.getElementsByTagName("item"))) { String itemName = getString(item); tempCont.item.add(itemName); } /* Put each container in the containers hashmap, the generic object hashmap, and the objectlookup hashmap*/ data.addObjectThroughLookup("container", tempCont); } private static void addItem(ZorkGame data, Element element) { final List prints = new ArrayList<>(); final List actions = new ArrayList<>(); NodeList turnOn = element.getElementsByTagName("turnon"); if (turnOn.getLength() > 0) { for (Element print : iterElements(element.getElementsByTagName("print"))) { prints.add(getString(print)); } for (Element action : iterElements(element.getElementsByTagName("action"))) { actions.add(getString(action)); } } /* Get all possible item attributes*/ ZorkItem tempItem = new ZorkItem( getItemOrDefault(element, "name", ""), getItemOrDefault(element, "description", ""), getItemOrDefault(element, "writing", ""), prints, actions ); readTriggersInObject(element, tempItem); /* Put each item in the items hashmap, the generic objects hashmap, and store its type in object lookup */ data.addObjectThroughLookup("item", tempItem); tempItem.updateStatus(getItemOrDefault(element, "status", "")); } private static void addRoom(ZorkGame data, Element element) { /*Get all possible Room attributes*/ ZorkRoom tempRoom = new ZorkRoom( getItemOrDefault(element, "name", ""), getItemOrDefault(element, "description", ""), getItemOrDefault(element, "type", "regular") ); readTriggersInObject(element, tempRoom); tempRoom.updateStatus(getItemOrDefault(element, "status", "")); for (Element item : iterElements(element.getElementsByTagName("item"))) { String itemName = getString(item); tempRoom.item.add(itemName); } for (Element creature : iterElements(element.getElementsByTagName("creature"))) { String creatureName = getString(creature); tempRoom.creature.add(creatureName); } for (Element container : iterElements(element.getElementsByTagName("container"))) { String containerName = getString(container); tempRoom.container.add(containerName); } for (Element border : iterElements(element.getElementsByTagName("border"))) { String borderDirection = getString((Element) border.getElementsByTagName("direction").item(0)); String borderName = getString((Element) border.getElementsByTagName("name").item(0)); tempRoom.border.put(borderDirection, borderName); } /*Add this room to the rooms hashmap, put it in the generic objects hashmap, and store it's type in the objectlookup hashmap*/ data.addObjectThroughLookup("room", tempRoom); } private static void readTriggersInObject(Element element, ZorkObject tempRoom) { for (Element trigger : iterElements(element.getElementsByTagName("trigger"))) { ZorkTrigger tempTrigger = new ZorkTrigger(getItemOrDefault(element, "type", "single")); for (Element command : iterElements(trigger.getElementsByTagName("command"))) { ZorkCommand tempCommand = new ZorkCommand(getString(command)); tempTrigger.conditions.add(tempCommand); tempTrigger.hasCommand = true; } readConditionsInTrigger(trigger, tempTrigger.conditions); for (Element print : iterElements(trigger.getElementsByTagName("print"))) { tempTrigger.print.add(getString(print)); } for (Element action : iterElements(trigger.getElementsByTagName("action"))) { tempTrigger.action.add(getString(action)); } tempRoom.trigger.add(tempTrigger); } } private static void readConditionsInTrigger(Element trigger, List conditionsList) { for (Element condition : iterElements(trigger.getElementsByTagName("condition"))) { NodeList object = condition.getElementsByTagName("object"); NodeList has = condition.getElementsByTagName("has"); if (has.getLength() > 0) { NodeList owner = condition.getElementsByTagName("owner"); ZorkConditionHas tempConditionHas = new ZorkConditionHas( getString((Element) has.item(0)), getString((Element) object.item(0)), getString((Element) owner.item(0)) ); conditionsList.add(tempConditionHas); } else { NodeList sstatus = condition.getElementsByTagName("status"); ZorkConditionStatus tempConditionStatus = new ZorkConditionStatus( getString((Element) sstatus.item(0)), getString((Element) object.item(0)) ); conditionsList.add(tempConditionStatus); } } } private static Iterable iterable(final NodeList nodeList) { return () -> new Iterator<>() { private int index = 0; @Override public boolean hasNext() { return index < nodeList.getLength(); } @Override public T next() { if (!hasNext()) throw new NoSuchElementException(); return (T) nodeList.item(index++); } }; } private static Iterable iterNodes(final NodeList nodeList) { return iterable(nodeList); } private static Iterable iterElements(final NodeList nodeList) { return iterable(nodeList); } public ZorkGame build() { ZorkGame data = new ZorkGame(); File file = new File(filename); if (!file.canRead()) { System.out.println("Error opening file. Exiting..."); throw new RuntimeException(); } try { /* Open the xml file*/ DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); // Limit XML features to mitigate vulnerabilities builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); builder.setFeature("http://xml.org/sax/features/external-general-entities", false); builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); Element rootElement = builder.newDocumentBuilder().parse(file).getDocumentElement(); /* Every single first generation child is a room, container, creature, or item. So load them in*/ for (Node node : iterNodes(rootElement.getChildNodes())) { Element element; if (node instanceof Element) { element = (Element) node; String tagType = element.getTagName(); switch (tagType) { /* If it's a room ... */ case "room": addRoom(data, element); break; /* If it's an item... */ case "item": addItem(data, element); break; /* If it's a container... */ case "container": addContainer(data, element); break; /* And finally, if it's a creature...*/ case "creature": addCreature(data, element); break; } } } } catch (Exception e) { System.out.println("Invalid XML file, exiting"); System.exit(-1); //e.printStackTrace(); } return data; } }