001package components.xmltree; 002 003import java.io.File; 004import java.io.IOException; 005import java.io.InputStreamReader; 006import java.io.Reader; 007import java.net.MalformedURLException; 008import java.net.URI; 009import java.net.URISyntaxException; 010import java.net.URL; 011import java.nio.charset.Charset; 012 013import javax.swing.JFrame; 014import javax.xml.parsers.DocumentBuilder; 015import javax.xml.parsers.DocumentBuilderFactory; 016import javax.xml.parsers.ParserConfigurationException; 017 018import org.w3c.dom.Document; 019import org.w3c.dom.NamedNodeMap; 020import org.w3c.dom.Node; 021import org.w3c.dom.NodeList; 022import org.xml.sax.InputSource; 023import org.xml.sax.SAXException; 024 025import components.map.Map; 026import components.map.Map1L; 027import components.sequence.Sequence; 028import components.set.Set; 029import components.set.Set1L; 030import components.tree.Tree; 031import components.tree.Tree1; 032 033/** 034 * {@code XMLTree} represented as a {@link Tree}, with implementations of all 035 * methods. 036 */ 037public class XMLTree2 extends XMLTreeSecondary { 038 039 /* 040 * Private members -------------------------------------------------------- 041 */ 042 043 /** 044 * Node for the tree representation. 045 */ 046 private static final class TreeNode { 047 048 /** 049 * The label (a tag or plain text). 050 */ 051 private String label; 052 053 /** 054 * Flag to record whether the label is a tag. 055 */ 056 private boolean isTag; 057 058 /** 059 * Map from attribute name to attribute value for the tag. 060 */ 061 private Map<String, String> attributes; 062 063 } 064 065 /** 066 * Tree representation for the {@code XMLTree}. 067 */ 068 private final Tree<TreeNode> rep; 069 070 /** 071 * The window to display the {@code XMLTree}. 072 */ 073 private JFrame frame; 074 075 /** 076 * Parses the XML input and constructs (and returns) the corresponding DOM 077 * tree. 078 * 079 * @param url 080 * the XML source (either a file or web URL) 081 * @return the XML DOM tree 082 */ 083 private Node getXML(URL url) { 084 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 085 DocumentBuilder db = null; 086 Document doc = null; 087 try { 088 db = dbf.newDocumentBuilder(); 089 InputSource is = new InputSource(url.toString()); 090 doc = db.parse(is); 091 } catch (ParserConfigurationException e) { 092 throw new AssertionError("Violation of: input is valid XML"); 093 } catch (SAXException e) { 094 throw new AssertionError("Violation of: input is valid XML"); 095 } catch (IOException e) { 096 // problem reading input: it may be an encoding issue 097 try { 098 String encoding = "UTF-8"; 099 if (Charset.defaultCharset().toString().equals(encoding)) { 100 encoding = "windows-1252"; 101 } 102 Reader reader = new InputStreamReader(url.openStream(), encoding); 103 InputSource is = new InputSource(reader); 104 doc = db.parse(is); 105 try { 106 reader.close(); 107 } catch (IOException e1) { 108 throw new AssertionError( 109 "Violation of: " + url.getFile() + " can be closed"); 110 } 111 } catch (MalformedURLException e1) { 112 throw new AssertionError("Violation of: input is valid XML"); 113 } catch (SAXException e1) { 114 throw new AssertionError("Violation of: input is valid XML"); 115 } catch (IOException e1) { 116 throw new AssertionError("Violation of: input is valid XML"); 117 } catch (NullPointerException e1) { // could occur on line 103? 118 throw new AssertionError("Violation of: input is valid XML"); 119 } 120 } 121 return doc.getDocumentElement(); 122 } 123 124 /** 125 * Constructs the XMLTree representation from the given {@code Node}. 126 * 127 * @param root 128 * the DOM tree to construct tree 129 * @param trimWhitespace 130 * flag to indicate whether leading and trailing whitespace 131 * should be trimmed from text leaves 132 * @return the new tree representation 133 */ 134 private static Tree<TreeNode> constructTree(Node root, boolean trimWhitespace) { 135 Tree<TreeNode> tree = new Tree1<TreeNode>(); 136 TreeNode tNode = new TreeNode(); 137 Sequence<Tree<TreeNode>> children = tree.newSequenceOfTree(); 138 if ((root.getNodeType() == Node.TEXT_NODE) 139 || (root.getNodeType() == Node.CDATA_SECTION_NODE)) { 140 String text = root.getTextContent(); 141 if (trimWhitespace) { 142 text = text.trim(); 143 } 144 tNode.label = text; 145 } else { 146 tNode.label = root.getNodeName(); 147 tNode.isTag = true; 148 tNode.attributes = constructAttributes(root); 149 150 NodeList nl = root.getChildNodes(); 151 for (int pos = 0; pos < nl.getLength(); pos++) { 152 Node child = nl.item(pos); 153 Tree<TreeNode> subtree = constructTree(child, trimWhitespace); 154 if (subtree.root().isTag) { 155 children.add(children.length(), subtree); 156 } else { 157 if (!subtree.root().label.equals("")) { 158 children.add(children.length(), subtree); 159 } 160 } 161 } 162 } 163 tree.assemble(tNode, children); 164 return tree; 165 } 166 167 /** 168 * Returns attribute map constructed from the given {@code Node}. 169 * 170 * @param root 171 * the DOM tree whose root's attributes will be returned in the 172 * attribute map 173 * @return the map of attribute (name, value) pairs 174 */ 175 private static Map<String, String> constructAttributes(Node root) { 176 Map<String, String> map = new Map1L<String, String>(); 177 NamedNodeMap nnm = root.getAttributes(); 178 if (nnm != null) { 179 for (int i = 0; i < nnm.getLength(); i++) { 180 Node a = nnm.item(i); 181 map.add(a.getNodeName(), a.getNodeValue()); 182 } 183 } 184 return map; 185 } 186 187 /** 188 * Constructor to be used by {@code child}. 189 * 190 * @param rep 191 * the representation for the new {@code XMLTree} 192 */ 193 private XMLTree2(Tree<TreeNode> rep) { 194 this.rep = rep; 195 } 196 197 /* 198 * Constructors ----------------------------------------------------------- 199 */ 200 201 /** 202 * Constructs an {@code XMLTree} from input {@code source} (could be a file 203 * or a URL). Leading and trailing whitespace is trimmed from text nodes. 204 * 205 * @param source 206 * XML input 207 * @requires source is the name of a file or a URL 208 * @ensures <pre> 209 * this = [the XMLTree corresponding to the given input source, 210 * with whitespace trimmed] 211 * </pre> 212 */ 213 public XMLTree2(String source) { 214 this(source, true); 215 } 216 217 /** 218 * Constructs an {@code XMLTree} from input {@code source} (could be a file 219 * or a URL). 220 * 221 * @param source 222 * XML input 223 * @param trimWhitespace 224 * flag to indicate whether leading and trailing whitespace 225 * should be trimmed from text nodes 226 * @requires source is the name of a file or a URL 227 * @ensures <pre> 228 * this = [the XMLTree corresponding to the given input source, 229 * with whitespace trimmed only if trimWhitespace] 230 * </pre> 231 */ 232 public XMLTree2(String source, boolean trimWhitespace) { 233 assert source != null : "Violation of: source is not null"; 234 URL url = null; 235 try { 236 url = new URI(source).toURL(); 237 } catch (URISyntaxException | MalformedURLException e) { 238 if (source.indexOf("://") != -1) { 239 // assume it was a URL 240 throw new AssertionError("Violation of: " + source + " is a valid URL"); 241 } 242 try { 243 File file = new File(source); 244 if (!file.exists()) { 245 throw new AssertionError("Violation of: " + source + " exists"); 246 } 247 if (!file.isFile()) { 248 throw new AssertionError("Violation of: " + source + " is a file"); 249 } 250 if (!file.canRead()) { 251 throw new AssertionError("Violation of: " + source + " is readable"); 252 } 253 url = file.toURI().toURL(); 254 } catch (MalformedURLException e1) { 255 throw new AssertionError( 256 "Violation of: " + source + " is a valid XML source"); 257 } 258 } 259 260 Node root = this.getXML(url); 261 this.rep = constructTree(root, trimWhitespace); 262 } 263 264 /* 265 * Kernel methods --------------------------------------------------------- 266 */ 267 268 @Override 269 public final String label() { 270 return this.rep.root().label; 271 } 272 273 @Override 274 public final boolean isTag() { 275 return this.rep.root().isTag; 276 } 277 278 @Override 279 public final boolean hasAttribute(String name) { 280 assert name != null : "Violation of: name is not null"; 281 assert this.rep.root().isTag 282 : "Violation of: the label of the root of this is a tag"; 283 return this.rep.root().attributes.hasKey(name); 284 } 285 286 @Override 287 public final String attributeValue(String name) { 288 assert name != null : "Violation of: name is not null"; 289 assert this.rep.root().isTag 290 : "Violation of: the label of the root of this is a tag"; 291 assert this.rep.root().attributes.hasKey(name) 292 : "Violation of: the root of this has an attribute called " + name; 293 return this.rep.root().attributes.value(name); 294 } 295 296 @Override 297 public final int numberOfChildren() { 298 assert this.rep.root().isTag 299 : "Violation of: the label of the root of this is a tag"; 300 Sequence<Tree<TreeNode>> children = this.rep.newSequenceOfTree(); 301 TreeNode root = this.rep.disassemble(children); 302 int length = children.length(); 303 this.rep.assemble(root, children); 304 return length; 305 } 306 307 @Override 308 public final XMLTree child(int k) { 309 assert this.rep.root().isTag 310 : "Violation of: the label of the root of this is a tag"; 311 assert k >= 0 : "Violation of: 0 <= k"; 312 assert k < this.rep.numberOfSubtrees() 313 : "Violation of: k < the number of subtrees of the root of this"; 314 Sequence<Tree<TreeNode>> children = this.rep.newSequenceOfTree(); 315 TreeNode root = this.rep.disassemble(children); 316 Tree<TreeNode> child = children.entry(k); 317 this.rep.assemble(root, children); 318 return new XMLTree2(child); 319 } 320 321 @Override 322 public final Iterable<String> attributeNames() { 323 assert this.rep.root().isTag 324 : "Violation of: the label of the root of this is a tag"; 325 Set<String> set = new Set1L<>(); 326 for (Map.Pair<String, String> pair : this.rep.root().attributes) { 327 set.add(pair.key()); 328 } 329 return set; 330 } 331 332 @Override 333 public final void display() { 334 this.display(XMLTreeFrame.DEFAULT_TITLE); 335 } 336 337 @Override 338 public final void display(String title) { 339 if (this.frame == null) { 340 this.frame = new XMLTreeFrame(this, title); 341 } 342 this.frame.setVisible(true); 343 } 344 345}