Patch - surrogate pair output support

"Dave Byrne" <[email protected]>
Newsgroups gmane.comp.java.jdom.general
Message-ID <F7F3B826D184FA458E7E2A7DAD18AB98025BD9D0@mdb-mail.MDBCapital.local>
Attached is a patch to provide support for outputting documents with
XMLOutputter that contain non-BMP utf-16 characters as surrogate pairs.

 

 

Patch is against cvs HEAD.

 

Thanks

Dave Byrne

_______________________________________________
To control your jdom-interest membership:
http://www.jdom.org/mailman/options/jdom-interest/[email protected]
surrogate.patch (application/octet-stream, 8.5 KB)
Index: org/jdom/Verifier.java
===================================================================
RCS file: /home/cvspublic/jdom/src/java/org/jdom/Verifier.java,v
retrieving revision 1.56
diff -u -r1.56 Verifier.java
--- org/jdom/Verifier.java	22 Nov 2007 07:00:38 -0000	1.56
+++ org/jdom/Verifier.java	5 Dec 2007 14:09:01 -0000
@@ -161,17 +161,17 @@
             int ch = text.charAt(i);
             
             // Check if high part of a surrogate pair
-            if (ch >= 0xD800 && ch <= 0xDBFF) {
+            if (isHighSurrogate((char) ch)) {
                 // Check if next char is the low-surrogate
                 i++;
                 if (i < len) {
                     char low = text.charAt(i);
-                    if (low < 0xDC00 || low > 0xDFFF) {
+                    if (!isLowSurrogate(low)) {
                         return "Illegal Surrogate Pair";
                     }
                     // It's a good pair, calculate the true value of
                     // the character to then fall thru to isXMLCharacter
-                    ch = 0x10000 + (ch - 0xD800) * 0x400 + (low - 0xDC00);
+                    ch = decodeSurrogatePair((char) ch, low);
                 }
                 else {
                     return "Surrogate Pair Truncated";
@@ -515,6 +515,16 @@
         // If we got here, everything is OK
         return null;
     }
+    /**
+     * This is a utility function to decode a non-BMP 
+     * UTF-16 surrogate pair.
+     * @param high high 16 bits
+     * @param low low 16 bits
+     * @return decoded character
+     */
+    public static int decodeSurrogatePair(char high, char low) {
+    	return 0x10000 + (high - 0xD800) * 0x400 + (low - 0xDC00);
+    }
 
     // [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] |
     // [-'()+,./:=?;*#@$_%]
@@ -692,6 +702,28 @@
 
         return false;
     }
+    
+    /**
+     * This is a function for determining whether the
+     * specified character is the high 16 bits in a 
+     * UTF-16 surrogate pair.
+     * @param ch character to check
+     * @return true if the character is a high surrogate, false otherwise
+     */
+    public static boolean isHighSurrogate(char ch) {
+    	return (ch >= 0xD800 && ch <= 0xDBFF);
+    }
+    
+    /**
+     * This is a function for determining whether the 
+     * specified character is the low 16 bits in a 
+     * UTF-16 surrogate pair.
+     * @param ch character to check
+     * @return true if the character is a low surrogate, false otherwise.
+     */
+    public static boolean isLowSurrogate(char ch) {
+    	return (ch >= 0xDC00 && ch <= 0xDFFF);
+    }
 
     /**
      * <p>
Index: org/jdom/output/XMLOutputter.java
===================================================================
RCS file: /home/cvspublic/jdom/src/java/org/jdom/output/XMLOutputter.java,v
retrieving revision 1.116
diff -u -r1.116 XMLOutputter.java
--- org/jdom/output/XMLOutputter.java	10 Nov 2007 05:29:01 -0000	1.116
+++ org/jdom/output/XMLOutputter.java	5 Dec 2007 14:09:01 -0000
@@ -1337,16 +1337,18 @@
      *
      * @param str <code>String</code> input to escape.
      * @return <code>String</code> with escaped content.
+     * @throws IOException if an entity can not be escaped
      */
-    public String escapeAttributeEntities(String str) {
+    public String escapeAttributeEntities(String str) throws IOException {
         StringBuffer buffer;
-        char ch;
+        int ch, pos;
         String entity;
         EscapeStrategy strategy = currentFormat.escapeStrategy;
 
         buffer = null;
         for (int i = 0; i < str.length(); i++) {
             ch = str.charAt(i);
+            pos = i;
             switch(ch) {
                 case '<' :
                     entity = "&lt;";
@@ -1375,7 +1377,25 @@
                     entity = "&#xA;";
                     break;
                 default :
-                    if (strategy.shouldEscape(ch)) {
+                	                
+                    if (strategy.shouldEscape((char) ch)) {       
+                    	//make sure what we are escaping is not the 
+                    	//beginning of a multi-byte character. 
+                    	if(Verifier.isHighSurrogate((char) ch)) {
+                    		//this is a the high of a surrogate pair
+                    		i++;                    		
+                    		if (i < str.length()) {
+                    			char low = str.charAt(i);
+                    			if(!Verifier.isLowSurrogate(low)) {
+                    				throw new IOException("Could not decode surrogate pair 0x" + 
+                    						Integer.toHexString(ch) + " / 0x" + Integer.toHexString(low));
+                    			}
+                    			ch = Verifier.decodeSurrogatePair((char) ch, low);
+                    		} else {
+                    			throw new IOException("Surrogate pair 0x" + 
+                						Integer.toHexString(ch) + " truncated");
+                    		}
+                    	}
                         entity = "&#x" + Integer.toHexString(ch) + ";";
                     }
                     else {
@@ -1390,13 +1410,13 @@
                     buffer = new StringBuffer(str.length() + 20);
                     // Copy previous skipped characters and fall through
                     // to pickup current character
-                    buffer.append(str.substring(0, i));
+                    buffer.append(str.substring(0, pos));
                     buffer.append(entity);
                 }
             }
             else {
                 if (entity == null) {
-                    buffer.append(ch);
+                    buffer.append((char) ch);
                 }
                 else {
                     buffer.append(entity);
@@ -1419,18 +1439,20 @@
      *
      * @param str <code>String</code> input to escape.
      * @return <code>String</code> with escaped content.
+     * @throws IOException if an entity can not be escaped
      */
-    public String escapeElementEntities(String str) {
+    public String escapeElementEntities(String str) throws IOException {
         if (escapeOutput == false) return str;
 
         StringBuffer buffer;
-        char ch;
+        int ch, pos;
         String entity;
         EscapeStrategy strategy = currentFormat.escapeStrategy;
 
         buffer = null;
         for (int i = 0; i < str.length(); i++) {
             ch = str.charAt(i);
+            pos = i;
             switch(ch) {
                 case '<' :
                     entity = "&lt;";
@@ -1448,7 +1470,26 @@
                     entity = currentFormat.lineSeparator;
                     break;
                 default :
-                    if (strategy.shouldEscape(ch)) {
+                
+                    if (strategy.shouldEscape((char) ch)) {
+                    	
+                    	//make sure what we are escaping is not the 
+                    	//beginning of a multi-byte character. 
+                    	if(Verifier.isHighSurrogate((char) ch)) {
+                    		//this is a the high of a surrogate pair
+                    		i++;
+                    		if (i < str.length()) {
+                    			char low = str.charAt(i);
+                    			if(!Verifier.isLowSurrogate(low)) {
+                    				throw new IOException("Could not decode surrogate pair 0x" + 
+                    						Integer.toHexString(ch) + " / 0x" + Integer.toHexString(low));
+                    			}
+                    			ch = Verifier.decodeSurrogatePair((char) ch, low);
+                    		} else {
+                    			throw new IOException("Surrogate pair 0x" + 
+                						Integer.toHexString(ch) + " truncated");
+                    		}
+                    	}
                         entity = "&#x" + Integer.toHexString(ch) + ";";
                     }
                     else {
@@ -1463,13 +1504,13 @@
                     buffer = new StringBuffer(str.length() + 20);
                     // Copy previous skipped characters and fall through
                     // to pickup current character
-                    buffer.append(str.substring(0, i));
+                    buffer.append(str.substring(0, pos));
                     buffer.append(entity);
                 }
             }
             else {
                 if (entity == null) {
-                    buffer.append(ch);
+                    buffer.append((char) ch);
                 }
                 else {
                     buffer.append(entity);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.