Author: rhauch
Date: 2008-10-27 23:01:03 -0400 (Mon, 27 Oct 2008)
New Revision: 590
Added:
trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlNameEncoder.java
trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlNameEncoderTest.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/xml/XmlHandler.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/xml/XmlHandlerTest.java
trunk/dna-graph/src/test/resources/xmlHandler/
trunk/dna-graph/src/test/resources/xmlHandler/docWithComments.xml
trunk/dna-graph/src/test/resources/xmlHandler/docWithNamespaces.xml
trunk/dna-graph/src/test/resources/xmlHandler/docWithNestedNamespaces.xml
trunk/dna-graph/src/test/resources/xmlHandler/docWithOnlyRootElement.xml
trunk/dna-graph/src/test/resources/xmlHandler/docWithoutNamespaces.xml
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
Log:
DNA-242 - Change the XML importer code to no longer use XmlSequencer
http://jira.jboss.com/jira/browse/DNA-242
Added new XmlHandler implementation (which subclasses DefaultHandler2 and overrides the
appropriate methods) with a unit test that verifies the behavior. It is designed to be
used with a regular org.xml.sax.XMLReader implementation, but processes the XML content
and creates the nodes that represent that content. And while the current component should
be perfect for importing XML content into a repository, it can also be subclassed with
other DefaultHandler2 methods being overridden to handle other parts of the XML content,
including XML comments, DTD entities, entity resolution, etc. This approach of
specializing a DefaultHandler2 implementation is much more extensible and customizable
than providing an "importer" component.
Added: trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlNameEncoder.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlNameEncoder.java
(rev 0)
+++
trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlNameEncoder.java 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,517 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.common.text;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.BitSet;
+
+/**
+ * An {@link TextEncoder encoder} and {@link TextDecoder decoder} for XML element and
attribute names.
+ * <p>
+ * Any UTF-16 unicode character that is not a valid XML name character according to the
<a
+ *
href="http://www.w3.org/TR/REC-xml/#sec-common-syn">World Wide Web
Consortium (W3C) Extensible Markup Language (XML) 1.0
+ * (Fourth Edition) Recommendation</a> is escaped as
<code>_xHHHH_</code>, where <code>HHHH</code> stands for the
four-digit
+ * hexadecimal UTF-16 unicode value for the character in the most significant bit first
order. For example, the name "Customer_ID"
+ * is encoded as "Customer_x0020_ID".
+ * </p>
+ * <p>
+ * Decoding transforms every <code>_xHHHH_</code> encoding sequences back
into the UTF-16 character. Note that
+ * {@link #decode(String) decoding} can be safely done on any XML name, even if the name
does not contain any encoded sequences.
+ * </p>
+ *
+ * @author Randall Hauch
+ */
+public class XmlNameEncoder implements TextDecoder, TextEncoder {
+
+ private static final BitSet XML_NAME_ALLOWED_CHARACTERS = new BitSet(2 ^ 16);
+
+ static {
+ // Initialize the unescaped bitset ...
+
+ // XML Names may contain: Letter | Digit | '.' | '-' |
'_' | ':' | CombiningChar | Extender
+ XML_NAME_ALLOWED_CHARACTERS.set('.');
+ XML_NAME_ALLOWED_CHARACTERS.set('-');
+ XML_NAME_ALLOWED_CHARACTERS.set('_');
+ XML_NAME_ALLOWED_CHARACTERS.set(':');
+
+ // XML Base Character Set
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0041', '\u005A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0061', '\u007A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u00C0', '\u00D6' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u00D8', '\u00F6' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u00F8', '\u00FF' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0100', '\u0131' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0134', '\u013E' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0141', '\u0148' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u014A', '\u017E' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0180', '\u01C3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u01CD', '\u01F0' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u01F4', '\u01F5' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u01FA', '\u0217' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0250', '\u02A8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u02BB', '\u02C1' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0386');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0388', '\u038A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u038C');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u038E', '\u03A1' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u03A3', '\u03CE' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u03D0', '\u03D6' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u03DA');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u03DC');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u03DE');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u03E0');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u03E2', '\u03F3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0401', '\u040C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u040E', '\u044F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0451', '\u045C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u045E', '\u0481' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0490', '\u04C4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u04C7', '\u04C8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u04CB', '\u04CC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u04D0', '\u04EB' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u04EE', '\u04F5' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u04F8', '\u04F9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0531', '\u0556' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0559');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0561', '\u0586' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u05D0', '\u05EA' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u05F0', '\u05F2' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0621', '\u063A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0641', '\u064A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0671', '\u06B7' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06BA', '\u06BE' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06C0', '\u06CE' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06D0', '\u06D3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06D5');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06E5', '\u06E6' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0905', '\u0939' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u093D');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0958', '\u0961' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0985', '\u098C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u098F', '\u0990' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0993', '\u09A8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09AA', '\u09B0' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09B2');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09B6', '\u09B9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09DC', '\u09DD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09DF', '\u09E1' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09F0', '\u09F1' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A05', '\u0A0A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A0F', '\u0A10' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A13', '\u0A28' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A2A', '\u0A30' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A32', '\u0A33' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A35', '\u0A36' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A38', '\u0A39' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A59', '\u0A5C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A5E');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A72', '\u0A74' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A85', '\u0A8B' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A8D');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A8F', '\u0A91' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A93', '\u0AA8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0AAA', '\u0AB0' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0AB2', '\u0AB3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0AB5', '\u0AB9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0ABD');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0AE0');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B05', '\u0B0C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B0F', '\u0B10' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B13', '\u0B28' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B2A', '\u0B30' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B32', '\u0B33' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B36', '\u0B39' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B3D');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B5C', '\u0B5D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B5F', '\u0B61' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B85', '\u0B8A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B8E', '\u0B90' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B92', '\u0B95' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B99', '\u0B9A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B9C');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B9E', '\u0B9F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BA3', '\u0BA4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BA8', '\u0BAA' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BAE', '\u0BB5' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BB7', '\u0BB9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C05', '\u0C0C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C0E', '\u0C10' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C12', '\u0C28' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C2A', '\u0C33' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C35', '\u0C39' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C60', '\u0C61' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C85', '\u0C8C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C8E', '\u0C90' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C92', '\u0CA8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CAA', '\u0CB3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CB5', '\u0CB9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CDE');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CE0', '\u0CE1' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D05', '\u0D0C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D0E', '\u0D10' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D12', '\u0D28' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D2A', '\u0D39' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D60', '\u0D61' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E01', '\u0E2E' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E30');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E32', '\u0E33' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E40', '\u0E45' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E81', '\u0E82' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E84');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E87', '\u0E88' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E8A');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E8D');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E94', '\u0E97' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E99', '\u0E9F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EA1', '\u0EA3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EA5');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EA7');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EAA', '\u0EAB' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EAD', '\u0EAE' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EB0');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EB2', '\u0EB3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EBD');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EC0', '\u0EC4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F40', '\u0F47' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F49', '\u0F69' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u10A0', '\u10C5' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u10D0', '\u10F6' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1100');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1102', '\u1103' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1105', '\u1107' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1109');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u110B', '\u110C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u110E', '\u1112' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u113C');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u113E');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1140');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u114C');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u114E');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1150');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1154', '\u1155' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1159');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u115F', '\u1161' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1163');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1165');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1167');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1169');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u116D', '\u116E' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1172', '\u1173' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1175');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u119E');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11A8');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11AB');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11AE', '\u11AF' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11B7', '\u11B8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11BA');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11BC', '\u11C2' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11EB');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11F0');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u11F9');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1E00', '\u1E9B' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1EA0', '\u1EF9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F00', '\u1F15' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F18', '\u1F1D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F20', '\u1F45' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F48', '\u1F4D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F50', '\u1F57' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F59');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F5B');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F5D');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F5F', '\u1F7D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1F80', '\u1FB4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FB6', '\u1FBC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FBE');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FC2', '\u1FC4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FC6', '\u1FCC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FD0', '\u1FD3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FD6', '\u1FDB' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FE0', '\u1FEC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FF2', '\u1FF4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u1FF6', '\u1FFC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u2126');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u212A', '\u212B' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u212E');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u2180', '\u2182' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u3041', '\u3094' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u30A1', '\u30FA' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u3105', '\u312C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\uAC00', '\uD7A3' + 1);
+
+ // XML Ideograph Character Set
+
+ XML_NAME_ALLOWED_CHARACTERS.set('\u4E00', '\u9FA5' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u3007');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u3021', '\u3029' + 1);
+
+ // XML Combining Character Set
+
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0300', '\u0345' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0360', '\u0361' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0483', '\u0486' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0591', '\u05A1' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u05A3', '\u05B9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u05BB', '\u05BD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u05BF');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u05C1', '\u05C2' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u05C4');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u064B', '\u0652' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0670');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06D6', '\u06DC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06DD', '\u06DF' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06E0', '\u06E4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06E7', '\u06E8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06EA', '\u06ED' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0901', '\u0903' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u093C');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u093E', '\u094C' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u094D');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0951', '\u0954' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0962', '\u0963' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0981', '\u0983' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09BC');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09BE');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09BF');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09C0', '\u09C4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09C7', '\u09C8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09CB', '\u09CD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09D7');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09E2', '\u09E3' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A02');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A3C');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A3E');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A3F');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A40', '\u0A42' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A47', '\u0A48' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A4B', '\u0A4D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A70', '\u0A71' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A81', '\u0A83' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0ABC');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0ABE', '\u0AC5' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0AC7', '\u0AC9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0ACB', '\u0ACD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B01', '\u0B03' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B3C');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B3E', '\u0B43' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B47', '\u0B48' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B4B', '\u0B4D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B56', '\u0B57' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B82', '\u0B83' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BBE', '\u0BC2' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BC6', '\u0BC8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BCA', '\u0BCD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BD7');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C01', '\u0C03' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C3E', '\u0C44' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C46', '\u0C48' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C4A', '\u0C4D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C55', '\u0C56' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C82', '\u0C83' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CBE', '\u0CC4' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CC6', '\u0CC8' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CCA', '\u0CCD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CD5', '\u0CD6' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D02', '\u0D03' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D3E', '\u0D43' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D46', '\u0D48' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D4A', '\u0D4D' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D57');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E31');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E34', '\u0E3A' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E47', '\u0E4E' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EB1');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EB4', '\u0EB9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EBB', '\u0EBC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EC8', '\u0ECD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F18', '\u0F19' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F35');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F37');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F39');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F3E');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F3F');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F71', '\u0F84' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F86', '\u0F8B' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F90', '\u0F95' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F97');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F99', '\u0FAD' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0FB1', '\u0FB7' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0FB9');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u20D0', '\u20DC' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u20E1');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u302A', '\u302F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u3099');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u309A');
+
+ // XML Digits
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0030', '\u0039' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0660', '\u0669' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u06F0', '\u06F9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0966', '\u096F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u09E6', '\u09EF' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0A66', '\u0A6F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0AE6', '\u0AEF' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0B66', '\u0B6F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0BE7', '\u0BEF' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0C66', '\u0C6F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0CE6', '\u0CEF' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0D66', '\u0D6F' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E50', '\u0E59' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0ED0', '\u0ED9' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0F20', '\u0F29' + 1);
+
+ // XML Extenders
+ XML_NAME_ALLOWED_CHARACTERS.set('\u00B7');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u02D0');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u02D1');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0387');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0640');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0E46');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u0EC6');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u3005');
+ XML_NAME_ALLOWED_CHARACTERS.set('\u3031', '\u3035' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u309D', '\u309E' + 1);
+ XML_NAME_ALLOWED_CHARACTERS.set('\u30FC', '\u30FE' + 1);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.common.text.TextDecoder#decode(java.lang.String)
+ */
+ public String decode( String encodedText ) {
+ if (encodedText == null) return null;
+ if (encodedText.length() < 7) {
+ // Not big enough to have an encoded sequence
+ return encodedText;
+ }
+ StringBuilder sb = new StringBuilder();
+ char[] digits = new char[4];
+ CharacterIterator iter = new StringCharacterIterator(encodedText);
+ for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
+ if (c == '_') {
+ // Read the next character, if there is one ...
+ char next = iter.next();
+ if (next == CharacterIterator.DONE) {
+ sb.append(c);
+ break;
+ }
+ // If the next character is not 'x', then these are just regular
characters ...
+ if (next != 'x') {
+ sb.append(c).append(next);
+ continue;
+ }
+ // Read the next 4 characters (digits) and another '_' character
...
+ digits[0] = iter.next();
+ if (digits[0] == CharacterIterator.DONE) {
+ sb.append(c).append(next);
+ break;
+ }
+ digits[1] = iter.next();
+ if (digits[1] == CharacterIterator.DONE) {
+ sb.append(c).append(next).append(digits, 0, 1);
+ break;
+ }
+ digits[2] = iter.next();
+ if (digits[2] == CharacterIterator.DONE) {
+ sb.append(c).append(next).append(digits, 0, 2);
+ break;
+ }
+ digits[3] = iter.next();
+ if (digits[3] == CharacterIterator.DONE) {
+ sb.append(c).append(next).append(digits, 0, 3);
+ break;
+ }
+ char underscore = iter.next();
+ if (underscore != '_') { // includes DONE
+ sb.append(c).append(next).append(digits, 0, 4);
+ if (underscore == CharacterIterator.DONE) break;
+ sb.append(underscore);
+ continue;
+ }
+ // We've read all 4 digits, including the trailing '_'
+ // Now parse into the resulting character
+ try {
+ sb.appendCodePoint(Integer.parseInt(new String(digits), 16));
+ } catch (NumberFormatException e) {
+ // code was not hexadecimal, so just write out the characters as is
...
+ sb.append(c).append(next).append(digits).append(underscore);
+ }
+ } else {
+ // Just append other characters ...
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.common.text.TextEncoder#encode(java.lang.String)
+ */
+ public String encode( String text ) {
+ if (text == null) return null;
+ if (text.length() == 0) return text;
+ StringBuilder sb = new StringBuilder();
+ String hex = null;
+ CharacterIterator iter = new StringCharacterIterator(text);
+ for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
+ if (c == '_') {
+ // Read the next character (if there is one) ...
+ char next = iter.next();
+ if (next == CharacterIterator.DONE) {
+ sb.append(c);
+ break;
+ }
+ // If the next character is not 'x', then these are just regular
characters ...
+ if (next != 'x') {
+ sb.append(c).append(next);
+ continue;
+ }
+ // The next character is 'x', so write out the '_'
character in encoded form ...
+ sb.append("_x005f_");
+ // And then write out the next character ...
+ sb.append(next);
+ } else if (XML_NAME_ALLOWED_CHARACTERS.get(c)) {
+ // Legal characters for an XML Name ...
+ sb.append(c);
+ } else {
+ // All other characters must be escaped with '_xHHHH_' where
'HHHH' is the hex string for the code point
+ hex = Integer.toHexString(c);
+ // The hex string excludes the leading '0's, so check the
character values so we know how many to prepend
+ if (c >= '\u0000' && c <= '\u000f') {
+ sb.append("_x000").append(hex);
+ } else if (c >= '\u0010' && c <= '\u00ff')
{
+ sb.append("_x00").append(hex);
+ } else if (c >= '\u0100' && c <= '\u0fff')
{
+ sb.append("_x0").append(hex);
+ } else {
+ sb.append("_x").append(hex);
+ }
+ sb.append('_');
+ }
+ }
+ return sb.toString();
+ }
+
+}
Property changes on:
trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlNameEncoder.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlNameEncoderTest.java
===================================================================
--- trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlNameEncoderTest.java
(rev 0)
+++
trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlNameEncoderTest.java 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,170 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.common.text;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class XmlNameEncoderTest {
+
+ private XmlNameEncoder encoder = new XmlNameEncoder();
+
+ @Before
+ public void beforeEach() {
+ }
+
+ protected void checkEncoding( String input,
+ String expected ) {
+ String output = this.encoder.encode(input);
+ assertThat(output, is(notNullValue()));
+ assertEquals(expected, output);
+ assertThat(output.length(), is(expected.length()));
+ assertThat(output, is(expected));
+
+ checkDecoding(output, input);
+ }
+
+ protected void checkForNoEncoding( String input ) {
+ String output = this.encoder.encode(input);
+ assertThat(output, is(notNullValue()));
+ assertEquals(input, output);
+ assertThat(output.length(), is(input.length()));
+ assertThat(output, is(input));
+
+ checkForNoDecoding(input);
+ }
+
+ protected void checkForNoDecoding( String input ) {
+ String output = this.encoder.decode(input);
+ assertThat(output, is(notNullValue()));
+ assertEquals(input, output);
+ assertThat(output.length(), is(input.length()));
+ assertThat(output, is(input));
+ }
+
+ protected void checkDecoding( String input,
+ String output ) {
+ String decoded = this.encoder.decode(input);
+ assertEquals(output, decoded);
+ assertThat(decoded.length(), is(output.length()));
+ assertThat(decoded, is(output));
+ }
+
+ @Test
+ public void shouldNotEncodeUnderscoreIfNotFollowedByLowercaseX() {
+ checkForNoEncoding("Employee_ID");
+ checkForNoEncoding("_Employee_");
+ checkForNoEncoding("Employee__ID");
+ }
+
+ @Test
+ public void shouldEncodeUnderscoreIfFollowedByLowercaseX() {
+ checkEncoding("Employee_x", "Employee_x005f_x");
+ checkEncoding("Employee_x0", "Employee_x005f_x0");
+ checkEncoding("Employee_x0022_", "Employee_x005f_x0022_");
+ }
+
+ @Test
+ public void shouldNotDecodeIfNotValidHexadecimalValue() {
+ checkForNoDecoding("_xH013_");
+ }
+
+ @Test
+ public void shouldNotDecodeIfNotValidEncodedFormat() {
+ checkForNoDecoding("_X0022_"); // No lowercase 'x'
+ checkForNoDecoding("x0022_"); // No leading '_'
+ checkForNoDecoding("_x0022a"); // No trailing '_'
+ }
+
+ @Test
+ public void shouldNotEncodeDigits() {
+ for (char c = '\u0030'; c <= '\u0039'; c++) { // digit
+ checkForNoEncoding("Employee" + c + "xyz");
+ }
+ }
+
+ @Test
+ public void shouldNotEncodeAlphabeticCharacters() {
+ for (char c = '\u0041'; c <= '\u005a'; c++) { // digit
+ checkForNoEncoding("Employee" + c + "xyz");
+ }
+ for (char c = '\u0061'; c <= '\u007a'; c++) { // digit
+ checkForNoEncoding("Employee" + c + "xyz");
+ }
+ }
+
+ @Test
+ public void shouldNotEncodePeriodOrDashOrUnderscoreCharacters() {
+ checkForNoEncoding("Employee.xyz");
+ checkForNoEncoding("Employee-xyz");
+ checkForNoEncoding("Employee:xyz");
+ checkForNoEncoding("Employee_abc");
+ }
+
+ @Test
+ public void shouldDecodeIfCompleteHexadecimal() {
+ checkDecoding("Employee_", "Employee_");
+ checkDecoding("Employee_x", "Employee_x");
+ checkDecoding("Employee_x0", "Employee_x0");
+ checkDecoding("Employee_x00", "Employee_x00");
+ checkDecoding("Employee_x002", "Employee_x002");
+ checkDecoding("Employee_x0022", "Employee_x0022");
+ checkDecoding("_", "_");
+ checkDecoding("_x", "_x");
+ checkDecoding("_x0", "_x0");
+ checkDecoding("_x00", "_x00");
+ checkDecoding("_x002", "_x002");
+ checkDecoding("_x0022", "_x0022");
+ }
+
+ @Test
+ public void shouldEncodeUnderscoreOnlyWhenFollowedByX() {
+ checkEncoding("Employee_xyz", "Employee_x005f_xyz");
+ checkEncoding("Employee_ayz", "Employee_ayz");
+ }
+
+ @Test
+ public void shouldEncodeNonAlphaNumericCharacters() {
+ checkEncoding("Employee!xyz", "Employee_x0021_xyz");
+ checkEncoding("Employee\"xyz", "Employee_x0022_xyz");
+ checkEncoding("Employee#xyz", "Employee_x0023_xyz");
+ checkEncoding("Employee$xyz", "Employee_x0024_xyz");
+ checkEncoding("Employee%xyz", "Employee_x0025_xyz");
+ checkEncoding("Employee&xyz", "Employee_x0026_xyz");
+ checkEncoding("Employee'xyz", "Employee_x0027_xyz");
+ checkEncoding("Employee(xyz", "Employee_x0028_xyz");
+ checkEncoding("Employee)xyz", "Employee_x0029_xyz");
+ checkEncoding("Employee*xyz", "Employee_x002a_xyz");
+ checkEncoding("Employee+xyz", "Employee_x002b_xyz");
+ checkEncoding("Employee,xyz", "Employee_x002c_xyz");
+ checkEncoding("Employee/xyz", "Employee_x002f_xyz");
+ checkEncoding("Employee\u0B9Bxyz", "Employee_x0b9b_xyz");
+ }
+
+}
Property changes on:
trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlNameEncoderTest.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2008-10-28 03:00:46 UTC
(rev 589)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2008-10-28 03:01:03 UTC
(rev 590)
@@ -527,6 +527,19 @@
}
/**
+ * Begin the request to create a node located at the supplied path. This request is
submitted to the repository immediately.
+ *
+ * @param at the path to the node that is to be created.
+ * @param properties the properties for the new node
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> create( Path at,
+ Iterable<Property> properties ) {
+ this.requestQueue.submit(new CreateNodeRequest(new Location(at), properties));
+ return nextGraph;
+ }
+
+ /**
* Set the properties on a node.
*
* @param properties the properties to set
@@ -1104,6 +1117,15 @@
};
}
+ /**
+ * Obtain the graph that this batch uses.
+ *
+ * @return the graph; never null
+ */
+ public Graph getGraph() {
+ return Graph.this;
+ }
+
protected final void assertNotExecuted() {
if (executed) {
throw new
IllegalStateException(GraphI18n.unableToAddMoreRequestsToAlreadyExecutedBatch.text());
@@ -1552,6 +1574,29 @@
* </p>
*
* @param at the path to the node that is to be created.
+ * @param properties the iterator over the properties for the new node
+ * @return the object that can be used to specify addition properties for the new
node to be copied or the location of the
+ * node where the node is to be created
+ */
+ public Create<BatchConjunction> create( Path at,
+ Iterable<Property> properties ) {
+ assertNotExecuted();
+ CreateAction<BatchConjunction> action = new
CreateAction<BatchConjunction>(nextRequests, requestQueue,
+
new Location(at));
+ for (Property property : properties) {
+ action.and(property);
+ }
+ return action;
+ }
+
+ /**
+ * Begin the request to create a node located at the supplied path.
+ * <p>
+ * Like all other methods on the {@link Batch}, the request will be performed
when the {@link #execute()} method is
+ * called.
+ * </p>
+ *
+ * @param at the path to the node that is to be created.
* @param property a property for the new node
* @return the object that can be used to specify addition properties for the new
node to be copied or the location of the
* node where the node is to be created
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/xml/XmlHandler.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/xml/XmlHandler.java
(rev 0)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/xml/XmlHandler.java 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,433 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.xml;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.xml.parsers.SAXParser;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.text.TextDecoder;
+import org.jboss.dna.common.text.XmlNameEncoder;
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.properties.Name;
+import org.jboss.dna.graph.properties.NameFactory;
+import org.jboss.dna.graph.properties.NamespaceRegistry;
+import org.jboss.dna.graph.properties.Path;
+import org.jboss.dna.graph.properties.PathFactory;
+import org.jboss.dna.graph.properties.Property;
+import org.jboss.dna.graph.properties.PropertyFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.ext.DefaultHandler2;
+
+/**
+ * A {@link DefaultHandler2} specialization that responds to XML content events by
creating the corresponding content in the
+ * supplied graph. This implementation ignores DTD entities, XML contents, and other XML
processing instructions. If other
+ * behavior is required, the appropriate methods can be overridden. (Which is why this
class extends <code>DefaultHandler2</code>,
+ * which has support for processing all the different parts of XML.
+ * <p>
+ * This class can be passed to the {@link SAXParser}'s {@link
SAXParser#parse(java.io.File, org.xml.sax.helpers.DefaultHandler)
+ * parse(..,DefaultHandler)} methods.
+ * </p>
+ *
+ * @author Randall Hauch
+ */
+@NotThreadSafe
+public class XmlHandler extends DefaultHandler2 {
+
+ /**
+ * The choices for how attributes that have no namespace prefix should be assigned a
namespace.
+ *
+ * @author Randall Hauch
+ */
+ public enum AttributeScoping {
+ /** The attribute's namespace is the default namespace */
+ USE_DEFAULT_NAMESPACE,
+ /** The attribute's namespace is the same namespace as the containing element
*/
+ INHERIT_ELEMENT_NAMESPACE;
+ }
+
+ /**
+ * Decoder for XML names, to turn '_xHHHH_' sequences in the XML element and
attribute names into the corresponding UTF-16
+ * characters.
+ */
+ public static TextDecoder DEFAULT_DECODER = new XmlNameEncoder();
+
+ /**
+ * The default {@link AttributeScoping}.
+ */
+ public static AttributeScoping DEFAULT_ATTRIBUTE_SCOPING =
AttributeScoping.USE_DEFAULT_NAMESPACE;
+
+ /**
+ * The destination where the content should be sent.
+ */
+ protected final Destination destination;
+
+ /**
+ * The name of the XML attribute whose value should be used for the name of the node.
For example, "jcr:name".
+ */
+ protected final Name nameAttribute;
+
+ /**
+ * The cached reference to the graph's path factory.
+ */
+ protected final PathFactory pathFactory;
+
+ /**
+ * The cached reference to the graph's name factory.
+ */
+ protected final NameFactory nameFactory;
+
+ /**
+ * The cached reference to the graph's property factory.
+ */
+ protected final PropertyFactory propertyFactory;
+
+ /**
+ * The cached reference to the graph's namespace registry.
+ */
+ protected final NamespaceRegistry namespaceRegistry;
+
+ /**
+ * The TextDecoder that is used to decode the names.
+ */
+ protected final TextDecoder decoder;
+
+ /**
+ * Local set of the namespace URIs that are registered. This is an optimization,
rather than relying upon the (thread-safe)
+ * {@link #namespaceRegistry}.
+ */
+ private final Set<String> namespaceUris = new HashSet<String>();
+
+ private final AttributeScoping attributeScoping;
+
+ /**
+ * The path for the node representing the current element. This starts out as the
path supplied by the constructor, and never
+ * is shorter than that initial path.
+ */
+ protected Path currentPath;
+
+ /**
+ * Flag the records whether the first element should be skipped.
+ */
+ protected boolean skipFirstElement;
+
+ /**
+ * A temporary list used to store the properties for a single node. This is cleared,
populated, then used to create the node.
+ */
+ protected final List<Property> properties = new ArrayList<Property>();
+
+ /**
+ * A working array that contains a single value object that is used to create
Property objects (without having to create an
+ * array of values for each property).
+ */
+ protected final Object[] propertyValues = new Object[1];
+
+ /**
+ * Create a handler that creates content in the supplied graph
+ *
+ * @param destination the destination where the content should be sent.graph in which
the content should be placed
+ * @param skipRootElement true if the root element of the document should be skipped,
or false if the root element should be
+ * converted to the top-level node of the content
+ * @param parent the path to the node in the graph under which the content should be
placed; if null, the root node is assumed
+ * @param textDecoder the text decoder that should be used to decode the XML element
names and XML attribute names, prior to
+ * using those values to create names; or null if the default encoder should
be used
+ * @param nameAttribute the name of the XML attribute whose value should be used for
the names of the nodes (typically, this
+ * is "jcr:name" or something equivalent); or null if the XML
element name should always be used as the node name
+ * @param scoping defines how to choose the namespace of attributes that do not have
a namespace prefix; if null, the
+ * {@link #DEFAULT_ATTRIBUTE_SCOPING} value is used
+ * @throws IllegalArgumentException if the destination reference is null
+ */
+ public XmlHandler( Destination destination,
+ boolean skipRootElement,
+ Path parent,
+ TextDecoder textDecoder,
+ Name nameAttribute,
+ AttributeScoping scoping ) {
+ CheckArg.isNotNull(destination, "destination");
+ assert destination != null;
+ this.destination = destination;
+ this.nameAttribute = nameAttribute;
+ this.decoder = textDecoder != null ? textDecoder : DEFAULT_DECODER;
+ this.skipFirstElement = skipRootElement;
+ this.attributeScoping = scoping != null ? scoping : DEFAULT_ATTRIBUTE_SCOPING;
+
+ // Set up references to frequently-used objects in the context ...
+ final ExecutionContext context = destination.getExecutionContext();
+ assert context != null;
+ this.nameFactory = context.getValueFactories().getNameFactory();
+ this.pathFactory = context.getValueFactories().getPathFactory();
+ this.propertyFactory = context.getPropertyFactory();
+ this.namespaceRegistry = context.getNamespaceRegistry();
+ assert this.nameFactory != null;
+ assert this.pathFactory != null;
+ assert this.propertyFactory != null;
+ assert this.namespaceRegistry != null;
+
+ // Set up the initial path ...
+ this.currentPath = parent != null ? parent : this.pathFactory.createRootPath();
+ assert this.currentPath != null;
+ }
+
+ /**
+ * Create a handler that creates content in the supplied graph
+ *
+ * @param graph the graph in which the content should be placed
+ * @param useBatch true if all of the actions to create the content in the graph
should be submitted to the graph in a single
+ * batch, or false if they should be submitted immediately after each is
identified
+ * @param skipRootElement true if the root element of the document should be skipped,
or false if the root element should be
+ * converted to the top-level node of the content
+ * @param parent the path to the node in the graph under which the content should be
placed; if null, the root node is assumed
+ * @param textDecoder the text decoder that should be used to decode the XML element
names and XML attribute names, prior to
+ * using those values to create names; or null if the default encoder should
be used
+ * @param nameAttribute the name of the XML attribute whose value should be used for
the names of the nodes (typically, this
+ * is "jcr:name" or something equivalent); or null if the XML
element name should always be used as the node name
+ * @param scoping defines how to choose the namespace of attributes that do not have
a namespace prefix; if null, the
+ * {@link #DEFAULT_ATTRIBUTE_SCOPING} value is used
+ * @throws IllegalArgumentException if the graph reference is null
+ */
+ public XmlHandler( Graph graph,
+ boolean useBatch,
+ boolean skipRootElement,
+ Path parent,
+ TextDecoder textDecoder,
+ Name nameAttribute,
+ AttributeScoping scoping ) {
+ this(createDestination(graph, useBatch), skipRootElement, parent, textDecoder,
nameAttribute, scoping);
+ }
+
+ protected static Destination createDestination( Graph graph,
+ boolean useBatch ) {
+ CheckArg.isNotNull(graph, "graph");
+ return useBatch ? new CreateOnGraphInBatches(graph.batch()) : new
CreateOnGraph(graph);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This method ensures that the namespace is registered with the {@link
NamespaceRegistry registry}, using the supplied prefix
+ * to register the namespace if required. Note that because this class does not
really use the namespace prefixes to create
+ * {@link Name} objects, no attempt is made to match the XML namespace prefixes.
+ * </p>
+ *
+ * @see org.xml.sax.helpers.DefaultHandler#startPrefixMapping(java.lang.String,
java.lang.String)
+ */
+ @Override
+ public void startPrefixMapping( String prefix,
+ String uri ) {
+ assert uri != null;
+ if (namespaceUris.add(uri)) {
+ // This is a new namespace for this document ...
+ if (!namespaceRegistry.isRegisteredNamespaceUri(uri)) {
+ if (prefix != null && prefix.length() == 0) prefix = null;
+ namespaceRegistry.register(prefix, uri);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
java.lang.String, java.lang.String,
+ * org.xml.sax.Attributes)
+ */
+ @Override
+ public void startElement( String uri,
+ String localName,
+ String name,
+ Attributes attributes ) {
+ // Should this (root) element be skipped?
+ if (skipFirstElement) {
+ skipFirstElement = false;
+ return;
+ }
+ assert localName != null;
+ Name nodeName = null;
+
+ properties.clear();
+ // Convert each of the attributes to a property ...
+ for (int i = 0; i != attributes.getLength(); ++i) {
+ String attributeLocalName = attributes.getLocalName(i);
+ String attributeUri = attributes.getURI(i);
+ Name attributeName = null;
+ if ((attributeUri == null || attributeUri.length() == 0) &&
attributes.getQName(i).indexOf(':') == -1) {
+ switch (this.attributeScoping) {
+ case INHERIT_ELEMENT_NAMESPACE:
+ attributeName = nameFactory.create(uri, attributeLocalName,
decoder);
+ break;
+ case USE_DEFAULT_NAMESPACE:
+ attributeName = nameFactory.create(attributeLocalName, decoder);
+ break;
+ }
+ } else {
+ attributeName = nameFactory.create(attributeUri, attributeLocalName,
decoder);
+ }
+ assert attributeName != null;
+ // Check to see if this is an attribute that represents the node name (which
may be null) ...
+ if (nodeName == null && attributeName.equals(nameAttribute)) {
+ nodeName = nameFactory.create(attributes.getValue(i)); // don't use a
decoder
+ continue;
+ }
+ // Create a property for this attribute ...
+ Property property = createProperty(attributeName, attributes.getValue(i));
+ properties.add(property);
+ }
+ // Create the node name if required ...
+ if (nodeName == null) nodeName = nameFactory.create(uri, localName, decoder);
+ // Update the current path ...
+ currentPath = pathFactory.create(currentPath, nodeName);
+ // Create the node, and note that we don't care about same-name siblings (as
the graph will correct them) ...
+ destination.create(currentPath, properties);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
java.lang.String, java.lang.String)
+ */
+ @Override
+ public void endElement( String uri,
+ String localName,
+ String name ) {
+ // Nothing to do but to change the current path to be the parent ...
+ currentPath = currentPath.getParent();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.xml.sax.helpers.DefaultHandler#endDocument()
+ */
+ @Override
+ public void endDocument() {
+ // Submit any outstanding requests (if there are any) ...
+ destination.submit();
+ }
+
+ /**
+ * Create a property with the given name and value, obtained from an attribute name
and value in the XML content.
+ * <p>
+ * By default, this method creates a property by directly using the value as the sole
String value of the property.
+ * </p>
+ *
+ * @param propertyName the name of the property; never null
+ * @param value the attribute value
+ * @return the property; may not be null
+ */
+ protected Property createProperty( Name propertyName,
+ String value ) {
+ propertyValues[0] = value;
+ return propertyFactory.create(propertyName, propertyValues);
+ }
+
+ /**
+ * Interface used internally as the destination for the requests. This is used to
abstract whether the requests should be
+ * submitted immediately or in a single batch.
+ *
+ * @author Randall Hauch
+ */
+ @NotThreadSafe
+ public static interface Destination {
+
+ /**
+ * Obtain the execution context of the destination.
+ *
+ * @return the destination's execution context
+ */
+ public ExecutionContext getExecutionContext();
+
+ /**
+ * Create a node at the supplied path and with the supplied attributes. The path
will be absolute.
+ *
+ * @param path the absolute path of the node
+ * @param properties the properties for the node; never null, but may be empty if
there are no properties
+ */
+ public void create( Path path,
+ List<Property> properties );
+
+ /**
+ * Signal to this destination that any enqueued create requests should be
submitted. Usually this happens at the end of
+ * the document parsing, but an implementer must allow for it to be called
multiple times and anytime during parsing.
+ */
+ public void submit();
+ }
+
+ @NotThreadSafe
+ protected final static class CreateOnGraph implements Destination {
+ private final Graph graph;
+
+ protected CreateOnGraph( final Graph graph ) {
+ assert graph != null;
+ this.graph = graph;
+ }
+
+ public ExecutionContext getExecutionContext() {
+ return graph.getContext();
+ }
+
+ public final void create( Path path,
+ List<Property> properties ) {
+ assert properties != null;
+ if (properties.isEmpty()) {
+ graph.create(path);
+ } else {
+ graph.create(path, properties);
+ }
+ }
+
+ public void submit() {
+ // Nothing to do, since each call to 'create' immediate executes on
the graph
+ }
+ }
+
+ @NotThreadSafe
+ protected final static class CreateOnGraphInBatches implements Destination {
+ private final Graph.Batch batch;
+
+ protected CreateOnGraphInBatches( Graph.Batch batch ) {
+ assert batch != null;
+ this.batch = batch;
+ }
+
+ public ExecutionContext getExecutionContext() {
+ return batch.getGraph().getContext();
+ }
+
+ public void create( Path path,
+ List<Property> properties ) {
+ assert properties != null;
+ if (properties.isEmpty()) {
+ batch.create(path);
+ } else {
+ batch.create(path, properties);
+ }
+ }
+
+ public void submit() {
+ batch.execute();
+ }
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/xml/XmlHandler.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-graph/src/test/java/org/jboss/dna/graph/xml/XmlHandlerTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/xml/XmlHandlerTest.java
(rev 0)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/xml/XmlHandlerTest.java 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,371 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This 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.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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 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 software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.xml;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import org.jboss.dna.common.text.Jsr283Encoder;
+import org.jboss.dna.common.text.TextDecoder;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connectors.BasicExecutionContext;
+import org.jboss.dna.graph.properties.Name;
+import org.jboss.dna.graph.properties.NamespaceRegistry;
+import org.jboss.dna.graph.properties.Path;
+import org.jboss.dna.graph.properties.PathFactory;
+import org.jboss.dna.graph.properties.Property;
+import org.jboss.dna.graph.requests.CreateNodeRequest;
+import org.junit.Before;
+import org.junit.Test;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * @author Randall Hauch
+ */
+public class XmlHandlerTest {
+
+ private static final String JCR_NAMESPACE_URI =
"http://www.jcp.org/jcr/1.0";
+
+ private XmlHandler handler;
+ private ExecutionContext context;
+ private XmlHandler.Destination destination;
+ private boolean skipRootElement = false;
+ private Path parentPath;
+ private TextDecoder decoder;
+ private Name nameAttribute;
+ private XmlHandler.AttributeScoping scoping;
+ private LinkedList<CreateNodeRequest> requests;
+
+ @Before
+ public void beforeEach() {
+ context = new BasicExecutionContext();
+ context.getNamespaceRegistry().register("jcr", JCR_NAMESPACE_URI);
+ destination = new RecordingDestination();
+ parentPath =
context.getValueFactories().getPathFactory().create("/a/b");
+ decoder = null;
+ nameAttribute =
context.getValueFactories().getNameFactory().create("jcr:name");
+ scoping = XmlHandler.AttributeScoping.USE_DEFAULT_NAMESPACE;
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotConstructInstanceWhenGivenNullDestination() {
+ destination = null;
+ new XmlHandler(destination, skipRootElement, parentPath, decoder, nameAttribute,
scoping);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotConstructInstanceWhenGivenNullGraph() {
+ Graph graph = null;
+ new XmlHandler(graph, true, skipRootElement, parentPath, decoder, nameAttribute,
scoping);
+ }
+
+ @Test
+ public void shouldUseDefaultDecoderIfNoneIsProvidedInConstructor() {
+ decoder = null;
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ assertThat(handler.destination, is(sameInstance(destination)));
+ assertThat(handler.currentPath, is(sameInstance(parentPath)));
+ assertThat(handler.skipFirstElement, is(skipRootElement));
+ assertThat(handler.decoder, is(sameInstance(XmlHandler.DEFAULT_DECODER)));
+ assertThat(handler.nameAttribute, is(sameInstance(nameAttribute)));
+ }
+
+ @Test
+ public void shouldUseDecoderProvidedInConstructor() {
+ decoder = new Jsr283Encoder();
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ assertThat(handler.destination, is(sameInstance(destination)));
+ assertThat(handler.currentPath, is(sameInstance(parentPath)));
+ assertThat(handler.skipFirstElement, is(skipRootElement));
+ assertThat(handler.decoder, is(sameInstance(decoder)));
+ assertThat(handler.nameAttribute, is(sameInstance(nameAttribute)));
+ }
+
+ @Test
+ public void shouldPlaceContentUnderRootIfNoPathIsProvidedInConstructor() {
+ parentPath = null;
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ assertThat(handler.destination, is(sameInstance(destination)));
+ assertThat(handler.currentPath.isRoot(), is(true));
+ assertThat(handler.skipFirstElement, is(skipRootElement));
+ assertThat(handler.decoder, is(sameInstance(decoder != null ? decoder :
XmlHandler.DEFAULT_DECODER)));
+ assertThat(handler.nameAttribute, is(sameInstance(nameAttribute)));
+ }
+
+ @Test
+ public void shouldNotLookForNameAttributeIfNoneIsProvidedInConstructor() {
+ nameAttribute = null;
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ assertThat(handler.destination, is(sameInstance(destination)));
+ assertThat(handler.currentPath, is(sameInstance(parentPath)));
+ assertThat(handler.skipFirstElement, is(skipRootElement));
+ assertThat(handler.decoder, is(sameInstance(decoder != null ? decoder :
XmlHandler.DEFAULT_DECODER)));
+ assertThat(handler.nameAttribute, is(nullValue()));
+ }
+
+ @Test
+ public void shouldParseXmlDocumentWithoutNamespaces() throws IOException,
SAXException {
+ parse("xmlHandler/docWithoutNamespaces.xml");
+ // Check the generated content; note that the attribute name doesn't match,
so the nodes don't get special names
+ assertNode("Cars");
+ assertNode("Cars/Hybrid");
+ assertNode("Cars/Hybrid/car", "name=Toyota Prius",
"maker=Toyota", "model=Prius");
+ assertNode("Cars/Hybrid/car", "name=Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode("Cars/Hybrid/car", "name=Nissan Altima",
"maker=Nissan", "model=Altima");
+ assertNode("Cars/Sports");
+ assertNode("Cars/Sports/car", "name=Aston Martin DB9",
"maker=Aston Martin", "model=DB9");
+ assertNode("Cars/Sports/car", "name=Infiniti G37",
"maker=Infiniti", "model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentWithNamespaces() throws IOException, SAXException
{
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ parse("xmlHandler/docWithNamespaces.xml");
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode("c:Cars");
+ assertNode("c:Cars/c:Hybrid");
+ assertNode("c:Cars/c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("c:Cars/c:Hybrid/Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode("c:Cars/c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("c:Cars/c:Sports");
+ assertNode("c:Cars/c:Sports/Aston Martin DB9", "maker=Aston
Martin", "model=DB9");
+ assertNode("c:Cars/c:Sports/Infiniti G37", "maker=Infiniti",
"model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentWithNestedNamespaceDeclarations() throws
IOException, SAXException {
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ context.getNamespaceRegistry().register("i",
"http://attributes.com");
+ parse("xmlHandler/docWithNestedNamespaces.xml");
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode("Cars");
+ assertNode("Cars/c:Hybrid");
+ assertNode("Cars/c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("Cars/c:Hybrid/Toyota Highlander", "maker=Toyota",
"model=Highlander");
+ assertNode("Cars/c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("Cars/Sports");
+ assertNode("Cars/Sports/Aston Martin DB9", "i:maker=Aston
Martin", "model=DB9");
+ assertNode("Cars/Sports/Infiniti G37", "i:maker=Infiniti",
"model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentWithNamespacePrefixesThatDoNotMatchRegistry()
throws IOException, SAXException {
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ parse("xmlHandler/docWithNamespaces.xml");
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode("c:Cars");
+ assertNode("c:Cars/c:Hybrid");
+ assertNode("c:Cars/c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("c:Cars/c:Hybrid/Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode("c:Cars/c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("c:Cars/c:Sports");
+ assertNode("c:Cars/c:Sports/Aston Martin DB9", "maker=Aston
Martin", "model=DB9");
+ assertNode("c:Cars/c:Sports/Infiniti G37", "maker=Infiniti",
"model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentWithNamespacesThatAreNotYetInRegistry() throws
IOException, SAXException {
+ NamespaceRegistry reg = context.getNamespaceRegistry();
+ reg.unregister(JCR_NAMESPACE_URI);
+ // Verify the prefixes don't exist ...
+ assertThat(reg.getPrefixForNamespaceUri(JCR_NAMESPACE_URI, false),
is(nullValue()));
+ assertThat(reg.getPrefixForNamespaceUri("http://default.namespace.com",
false), is(nullValue()));
+ // Parse the XML file ...
+ parse("xmlHandler/docWithNestedNamespaces.xml");
+ // Get the prefix for the default namespace ...
+ String c = reg.getPrefixForNamespaceUri("http://default.namespace.com",
false);
+ String i = reg.getPrefixForNamespaceUri("http://attributes.com",
false);
+ String d = reg.getPrefixForNamespaceUri(reg.getDefaultNamespaceUri(), false);
+ if (c.length() != 0) c = c + ":";
+ if (d.length() != 0) d = d + ":";
+ if (i.length() != 0) i = i + ":";
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode(d + "Cars");
+ assertNode(d + "Cars/" + c + "Hybrid");
+ assertNode(d + "Cars/" + c + "Hybrid/Toyota Prius",
"maker=Toyota", "model=Prius");
+ assertNode(d + "Cars/" + c + "Hybrid/Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode(d + "Cars/" + c + "Hybrid/Nissan Altima",
"maker=Nissan", "model=Altima");
+ assertNode(d + "Cars/" + d + "Sports");
+ assertNode(d + "Cars/" + d + "Sports/Aston Martin DB9", i +
"maker=Aston Martin", "model=DB9");
+ assertNode(d + "Cars/" + d + "Sports/Infiniti G37", i +
"maker=Infiniti", "model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentThatUsesNameAttribute() throws IOException,
SAXException {
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ parse("xmlHandler/docWithNamespaces.xml");
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode("c:Cars");
+ assertNode("c:Cars/c:Hybrid");
+ assertNode("c:Cars/c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("c:Cars/c:Hybrid/Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode("c:Cars/c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("c:Cars/c:Sports");
+ assertNode("c:Cars/c:Sports/Aston Martin DB9", "maker=Aston
Martin", "model=DB9");
+ assertNode("c:Cars/c:Sports/Infiniti G37", "maker=Infiniti",
"model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentThatContainsNoContent() throws IOException,
SAXException {
+ parse("xmlHandler/docWithOnlyRootElement.xml");
+ assertNode("Cars");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentAndShouldNotCreateNodeForRootElement() throws
IOException, SAXException {
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ parentPath = null;
+ skipRootElement = true;
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ parse("xmlHandler/docWithNamespaces.xml");
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode("c:Hybrid");
+ assertNode("c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("c:Hybrid/Toyota Highlander", "maker=Toyota",
"model=Highlander");
+ assertNode("c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("c:Sports");
+ assertNode("c:Sports/Aston Martin DB9", "maker=Aston Martin",
"model=DB9");
+ assertNode("c:Sports/Infiniti G37", "maker=Infiniti",
"model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentAndShouldPlaceContentUnderNonRootNode() throws
IOException, SAXException {
+ parentPath =
context.getValueFactories().getPathFactory().create("/a/b");
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ parse("xmlHandler/docWithNamespaces.xml");
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode("c:Cars");
+ assertNode("c:Cars/c:Hybrid");
+ assertNode("c:Cars/c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("c:Cars/c:Hybrid/Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode("c:Cars/c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("c:Cars/c:Sports");
+ assertNode("c:Cars/c:Sports/Aston Martin DB9", "maker=Aston
Martin", "model=DB9");
+ assertNode("c:Cars/c:Sports/Infiniti G37", "maker=Infiniti",
"model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentAndShouldPlaceContentUnderRootNode() throws
IOException, SAXException {
+ parentPath = null;
+ handler = new XmlHandler(destination, skipRootElement, parentPath, decoder,
nameAttribute, scoping);
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ parse("xmlHandler/docWithNamespaces.xml");
+ // Check the generated content; note that the attribute name DOES match, so the
nodes names come from "jcr:name" attribute
+ assertNode("c:Cars");
+ assertNode("c:Cars/c:Hybrid");
+ assertNode("c:Cars/c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("c:Cars/c:Hybrid/Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode("c:Cars/c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("c:Cars/c:Sports");
+ assertNode("c:Cars/c:Sports/Aston Martin DB9", "maker=Aston
Martin", "model=DB9");
+ assertNode("c:Cars/c:Sports/Infiniti G37", "maker=Infiniti",
"model=G37");
+ }
+
+ @Test
+ public void shouldParseXmlDocumentWithXmlComments() throws IOException, SAXException
{
+ context.getNamespaceRegistry().register("c",
"http://default.namespace.com");
+ parse("xmlHandler/docWithComments.xml");
+ assertNode("c:Cars");
+ assertNode("c:Cars/c:Hybrid");
+ assertNode("c:Cars/c:Hybrid/Toyota Prius", "maker=Toyota",
"model=Prius");
+ assertNode("c:Cars/c:Hybrid/Toyota Highlander",
"maker=Toyota", "model=Highlander");
+ assertNode("c:Cars/c:Hybrid/Nissan Altima", "maker=Nissan",
"model=Altima");
+ assertNode("c:Cars/c:Sports");
+ assertNode("c:Cars/c:Sports/Aston Martin DB9", "maker=Aston
Martin", "model=DB9");
+ assertNode("c:Cars/c:Sports/Infiniti G37", "maker=Infiniti",
"model=G37");
+ }
+
+ protected void assertNode( String path,
+ String... properties ) {
+ // Create the expected path ...
+ PathFactory factory = context.getValueFactories().getPathFactory();
+ Path expectedPath = parentPath != null ? factory.create(parentPath, path) :
factory.create("/" + path);
+ // Create the list of properties ...
+ Map<Name, Property> expectedProperties = new HashMap<Name,
Property>();
+ for (String propertyString : properties) {
+ String[] strings = propertyString.split("=");
+ if (strings.length < 2) continue;
+ Name name = context.getValueFactories().getNameFactory().create(strings[0]);
+ Object[] values = new Object[strings.length - 1];
+ for (int i = 1; i != strings.length; ++i) {
+ values[i - 1] = strings[i];
+ }
+ Property property = context.getPropertyFactory().create(name, values);
+ expectedProperties.put(name, property);
+ }
+ // Now get the next request and compare the expected and actual ...
+ CreateNodeRequest request = requests.remove();
+ assertThat(request.at().getPath(), is(expectedPath));
+ for (Property actual : request.properties()) {
+ Property expected = expectedProperties.remove(actual.getName());
+ assertThat(expected, is(notNullValue()));
+ assertThat(actual, is(expected));
+ }
+ assertThat(expectedProperties.isEmpty(), is(true));
+ }
+
+ protected void parse( String relativePathToXmlFile ) throws IOException, SAXException
{
+ InputStream stream =
getClass().getClassLoader().getResourceAsStream(relativePathToXmlFile);
+ try {
+ XMLReader reader = XMLReaderFactory.createXMLReader();
+ reader.setContentHandler(handler);
+ reader.setErrorHandler(handler);
+ reader.parse(new InputSource(stream));
+ } finally {
+ if (stream != null) stream.close();
+ }
+ }
+
+ protected class RecordingDestination implements XmlHandler.Destination {
+ private final LinkedList<CreateNodeRequest> requests = new
LinkedList<CreateNodeRequest>();
+
+ public void create( Path path,
+ List<Property> properties ) {
+ requests.add(new CreateNodeRequest(new Location(path), properties));
+ }
+
+ @SuppressWarnings( "synthetic-access" )
+ public ExecutionContext getExecutionContext() {
+ return XmlHandlerTest.this.context;
+ }
+
+ @SuppressWarnings( "synthetic-access" )
+ public void submit() {
+ XmlHandlerTest.this.requests = requests;
+ }
+ }
+}
Property changes on:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/xml/XmlHandlerTest.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-graph/src/test/resources/xmlHandler/docWithComments.xml
===================================================================
--- trunk/dna-graph/src/test/resources/xmlHandler/docWithComments.xml
(rev 0)
+++ trunk/dna-graph/src/test/resources/xmlHandler/docWithComments.xml 2008-10-28 03:01:03
UTC (rev 590)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Cars xmlns="http://default.namespace.com"
xmlns:jcr="http://www.jcp.org/jcr/1.0">
+ <!-- This is a comment -->
+ <Hybrid>
+ <car jcr:name="Toyota Prius" maker="Toyota"
model="Prius"/>
+ <car jcr:name="Toyota Highlander" maker="Toyota"
model="Highlander"/>
+ <car jcr:name="Nissan Altima" maker="Nissan"
model="Altima"/>
+ </Hybrid>
+ <!--
+ This is another comment
+ -->
+ <Sports>
+ <car jcr:name="Aston Martin DB9" maker="Aston Martin"
model="DB9"/>
+ <car jcr:name="Infiniti G37" maker="Infiniti"
model="G37" />
+ </Sports>
+</Cars>
\ No newline at end of file
Property changes on: trunk/dna-graph/src/test/resources/xmlHandler/docWithComments.xml
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-graph/src/test/resources/xmlHandler/docWithNamespaces.xml
===================================================================
--- trunk/dna-graph/src/test/resources/xmlHandler/docWithNamespaces.xml
(rev 0)
+++ trunk/dna-graph/src/test/resources/xmlHandler/docWithNamespaces.xml 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Cars xmlns="http://default.namespace.com"
xmlns:jcr="http://www.jcp.org/jcr/1.0">
+ <Hybrid>
+ <car jcr:name="Toyota Prius" maker="Toyota"
model="Prius"/>
+ <car jcr:name="Toyota Highlander" maker="Toyota"
model="Highlander"/>
+ <car jcr:name="Nissan Altima" maker="Nissan"
model="Altima"/>
+ </Hybrid>
+ <Sports>
+ <car jcr:name="Aston Martin DB9" maker="Aston Martin"
model="DB9"/>
+ <car jcr:name="Infiniti G37" maker="Infiniti"
model="G37" />
+ </Sports>
+</Cars>
\ No newline at end of file
Property changes on: trunk/dna-graph/src/test/resources/xmlHandler/docWithNamespaces.xml
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-graph/src/test/resources/xmlHandler/docWithNestedNamespaces.xml
===================================================================
--- trunk/dna-graph/src/test/resources/xmlHandler/docWithNestedNamespaces.xml
(rev 0)
+++ trunk/dna-graph/src/test/resources/xmlHandler/docWithNestedNamespaces.xml 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Cars
xmlns:jcr="http://www.jcp.org/jcr/1.0">
+ <Hybrid xmlns="http://default.namespace.com">
+ <car jcr:name="Toyota Prius" maker="Toyota"
model="Prius"/>
+ <car jcr:name="Toyota Highlander" maker="Toyota"
model="Highlander"/>
+ <car jcr:name="Nissan Altima" maker="Nissan"
model="Altima"/>
+ </Hybrid>
+ <Sports
xmlns:jcr2="http://www.jcp.org/jcr/1.0"
xmlns:info="http://attributes.com">
+ <car jcr2:name="Aston Martin DB9" info:maker="Aston
Martin" model="DB9"/>
+ <car jcr:name="Infiniti G37" info:maker="Infiniti"
model="G37" />
+ </Sports>
+</Cars>
\ No newline at end of file
Property changes on:
trunk/dna-graph/src/test/resources/xmlHandler/docWithNestedNamespaces.xml
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-graph/src/test/resources/xmlHandler/docWithOnlyRootElement.xml
===================================================================
--- trunk/dna-graph/src/test/resources/xmlHandler/docWithOnlyRootElement.xml
(rev 0)
+++ trunk/dna-graph/src/test/resources/xmlHandler/docWithOnlyRootElement.xml 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Cars>
+</Cars>
\ No newline at end of file
Property changes on:
trunk/dna-graph/src/test/resources/xmlHandler/docWithOnlyRootElement.xml
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-graph/src/test/resources/xmlHandler/docWithoutNamespaces.xml
===================================================================
--- trunk/dna-graph/src/test/resources/xmlHandler/docWithoutNamespaces.xml
(rev 0)
+++ trunk/dna-graph/src/test/resources/xmlHandler/docWithoutNamespaces.xml 2008-10-28
03:01:03 UTC (rev 590)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Cars>
+ <Hybrid>
+ <car name="Toyota Prius" maker="Toyota"
model="Prius"/>
+ <car name="Toyota Highlander" maker="Toyota"
model="Highlander"/>
+ <car name="Nissan Altima" maker="Nissan"
model="Altima"/>
+ </Hybrid>
+ <Sports>
+ <car name="Aston Martin DB9" maker="Aston Martin"
model="DB9"/>
+ <car name="Infiniti G37" maker="Infiniti"
model="G37" />
+ </Sports>
+</Cars>
\ No newline at end of file
Property changes on:
trunk/dna-graph/src/test/resources/xmlHandler/docWithoutNamespaces.xml
___________________________________________________________________
Name: svn:mime-type
+ text/plain