001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.om.FastStringBuffer;
004: import net.sf.saxon.om.XMLChar;
005: import net.sf.saxon.sort.IntHashMap;
006: import net.sf.saxon.sort.IntIterator;
007: import net.sf.saxon.trans.XPathException;
008:
009: import java.util.List;
010:
011: /**
012: * CharacterMapExpander: This ProxyReceiver expands characters occurring in a character map,
013: * as specified by the XSLT 2.0 xsl:character-map declaration
014: *
015: * @author Michael Kay
016: */
017:
018: public class CharacterMapExpander extends ProxyReceiver {
019:
020: private IntHashMap charMap;
021: private int min = Integer.MAX_VALUE; // the lowest mapped character
022: private int max = 0; // the highest mapped character
023: private boolean useNullMarkers = true;
024:
025: /**
026: * Set the character maps to be used by this CharacterMapExpander.
027: * They are merged into a single character map if there is more than one.
028: */
029:
030: public void setCharacterMaps(List maps) {
031: // merge the character maps, allowing definitions in a later map
032: // to overwrite definitions in an earlier map. (Note, we don't really
033: // need to do this if there is only one map, but we want to scan the keys
034: // anyway to extract the mimimum and maximum mapped characters.)
035:
036: charMap = new IntHashMap(64);
037: for (int i = 0; i < maps.size(); i++) {
038: IntHashMap hashMap = (IntHashMap) maps.get(i);
039: IntIterator keys = hashMap.keyIterator();
040: while (keys.hasNext()) {
041: int next = keys.next();
042: if (next < min) {
043: min = next;
044: }
045: if (next > max) {
046: max = next;
047: }
048: charMap.put(next, hashMap.get(next));
049: }
050: }
051: if (min > 0xD800) {
052: // if all the mapped characters are above the BMP, we need to check
053: // surrogates
054: min = 0xD800;
055: }
056: }
057:
058: /**
059: * Indicate whether the result of character mapping should be marked using NUL
060: * characters to prevent subsequent XML or HTML character escaping
061: */
062:
063: public void setUseNullMarkers(boolean use) {
064: useNullMarkers = use;
065: }
066:
067: /**
068: * Output an attribute
069: */
070:
071: public void attribute(int nameCode, int typeCode,
072: CharSequence value, int locationId, int properties)
073: throws XPathException {
074: //if ((properties & ReceiverOptions.DISABLE_CHARACTER_MAPS) == 0) {
075: CharSequence mapped = map(value, useNullMarkers);
076: if (mapped == value) {
077: // no mapping was done
078: super .attribute(nameCode, typeCode, value, locationId,
079: properties);
080: } else {
081: super .attribute(nameCode, typeCode, mapped, locationId,
082: (properties | ReceiverOptions.USE_NULL_MARKERS)
083: & ~ReceiverOptions.NO_SPECIAL_CHARS);
084: }
085: // } else {
086: // super.attribute(nameCode, typeCode, value, locationId, properties);
087: // }
088: }
089:
090: /**
091: * Output character data
092: */
093:
094: public void characters(CharSequence chars, int locationId,
095: int properties) throws XPathException {
096:
097: if ((properties & ReceiverOptions.DISABLE_ESCAPING) == 0) {
098: super .characters(map(chars, useNullMarkers), locationId,
099: (properties | ReceiverOptions.USE_NULL_MARKERS)
100: & ~ReceiverOptions.NO_SPECIAL_CHARS);
101: } else {
102: // if the user requests disable-output-escaping, this overrides the character
103: // mapping
104: super .characters(chars, locationId, properties);
105: }
106:
107: }
108:
109: /**
110: * Perform the character mappping
111: * @param in the input string to be mapped
112: * @param insertNulls true if null (0) characters are to be inserted before
113: * and after replacement characters. This is done in attribute values to signal
114: * that output escaping of these characters is disabled.
115: */
116:
117: private CharSequence map(CharSequence in, boolean insertNulls) {
118:
119: // First scan the string to see if there are any possible mapped
120: // characters; if not, don't bother creating the new buffer
121:
122: boolean move = false;
123: for (int i = 0; i < in.length();) {
124: char c = in.charAt(i++);
125: if (c >= min && c <= max) {
126: move = true;
127: break;
128: }
129: }
130: if (!move) {
131: return in;
132: }
133:
134: FastStringBuffer buffer = new FastStringBuffer(in.length() * 2);
135: int i = 0;
136: while (i < in.length()) {
137: char c = in.charAt(i++);
138: if (c >= min && c <= max) {
139: if (XMLChar.isHighSurrogate(c)) {
140: // assume the string is properly formed
141: char d = in.charAt(i++);
142: int s = XMLChar.supplemental(c, d);
143: String rep = (String) charMap.get(s);
144: if (rep == null) {
145: buffer.append(c);
146: buffer.append(d);
147: } else {
148: if (insertNulls) {
149: buffer.append((char) 0);
150: buffer.append(rep);
151: buffer.append((char) 0);
152: } else {
153: buffer.append(rep);
154: }
155: }
156: } else {
157: String rep = (String) charMap.get(c);
158: if (rep == null) {
159: buffer.append(c);
160: } else {
161: if (insertNulls) {
162: buffer.append((char) 0);
163: buffer.append(rep);
164: buffer.append((char) 0);
165: } else {
166: buffer.append(rep);
167: }
168: }
169: }
170: } else {
171: buffer.append(c);
172: }
173: }
174: return buffer;
175: }
176:
177: };
178:
179: //
180: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
181: // you may not use this file except in compliance with the License. You may obtain a copy of the
182: // License at http://www.mozilla.org/MPL/
183: //
184: // Software distributed under the License is distributed on an "AS IS" basis,
185: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
186: // See the License for the specific language governing rights and limitations under the License.
187: //
188: // The Original Code is: all this file.
189: //
190: // The Initial Developer of the Original Code is Michael H. Kay
191: //
192: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
193: //
194: // Contributor(s): none.
195: //
|