001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.om.AttributeCollectionImpl;
004: import net.sf.saxon.om.NamePool;
005: import net.sf.saxon.om.NamespaceConstant;
006: import net.sf.saxon.style.StandardNames;
007: import net.sf.saxon.trans.XPathException;
008:
009: import javax.xml.transform.OutputKeys;
010: import java.util.Properties;
011:
012: /**
013: * The MetaTagAdjuster adds a meta element to the content of the head element, indicating
014: * the required content type and encoding; it also removes any existing meta element
015: * containing this information
016: */
017:
018: public class MetaTagAdjuster extends ProxyReceiver {
019:
020: boolean seekingHead = true;
021: int droppingMetaTags = -1;
022: boolean inMetaTag = false;
023: boolean foundHead = false;
024: int metaCode;
025: short requiredURICode = 0;
026: AttributeCollectionImpl attributes;
027: String encoding;
028: String mediaType;
029: int level = 0;
030: boolean isXHTML = false;
031:
032: /**
033: * Set output properties
034: */
035:
036: public void setOutputProperties(Properties details)
037: throws XPathException {
038: encoding = details.getProperty(OutputKeys.ENCODING);
039: if (encoding == null) {
040: encoding = "UTF-8";
041: }
042: mediaType = details.getProperty(OutputKeys.MEDIA_TYPE);
043: if (mediaType == null) {
044: mediaType = "text/html";
045: }
046: }
047:
048: /**
049: * Indicate whether we're handling HTML or XHTML
050: */
051:
052: public void setIsXHTML(boolean xhtml) {
053: isXHTML = xhtml;
054: if (xhtml) {
055: requiredURICode = getNamePool().getCodeForURI(
056: NamespaceConstant.XHTML);
057: } else {
058: requiredURICode = 0;
059: }
060: }
061:
062: /**
063: * Compare a name: case-blindly in the case of HTML, case-sensitive for XHTML
064: */
065:
066: private boolean comparesEqual(String name1, String name2) {
067: if (isXHTML) {
068: return name1.equals(name2);
069: } else {
070: return name1.equalsIgnoreCase(name2);
071: }
072:
073: }
074:
075: /**
076: * Notify the start of an element
077: *
078: * @param nameCode integer code identifying the name of the element within the name pool.
079: * @param typeCode integer code identifying the element's type within the name pool.
080: * @param properties properties of the element node
081: */
082:
083: public void startElement(int nameCode, int typeCode,
084: int locationId, int properties) throws XPathException {
085: if (droppingMetaTags == level) {
086: metaCode = nameCode;
087: int uriCode = getNamePool().getURICode(nameCode);
088: String localName = getNamePool().getLocalName(nameCode);
089: if (uriCode == requiredURICode
090: && comparesEqual(localName, "meta")) {
091: inMetaTag = true;
092: attributes.clear();
093: return;
094: }
095: }
096: level++;
097: super .startElement(nameCode, typeCode, locationId, properties);
098: if (seekingHead) {
099: NamePool namePool = getNamePool();
100: int uriCode = namePool.getURICode(nameCode);
101: String localName = namePool.getLocalName(nameCode);
102: if (uriCode == requiredURICode
103: && comparesEqual(localName, "head")) {
104: foundHead = true;
105: }
106: }
107:
108: }
109:
110: /**
111: * Notify an attribute. Attributes are notified after the startElement event, and before any
112: * children. Namespaces and attributes may be intermingled.
113: *
114: * @param nameCode The name of the attribute, as held in the name pool
115: * @param typeCode The type of the attribute, as held in the name pool
116: * @param properties Bit significant value. The following bits are defined:
117: * <dd>DISABLE_ESCAPING</dd> <dt>Disable escaping for this attribute</dt>
118: * <dd>NO_SPECIAL_CHARACTERS</dd> <dt>Attribute value contains no special characters</dt>
119: * @throws IllegalStateException: attempt to output an attribute when there is no open element
120: * start tag
121: */
122:
123: public void attribute(int nameCode, int typeCode,
124: CharSequence value, int locationId, int properties)
125: throws XPathException {
126: if (inMetaTag) {
127: attributes.addAttribute(nameCode, typeCode, value
128: .toString(), locationId, properties);
129: } else {
130: super .attribute(nameCode, typeCode, value, locationId,
131: properties);
132: }
133: }
134:
135: /**
136: * Notify the start of the content, that is, the completion of all attributes and namespaces.
137: * Note that the initial receiver of output from XSLT instructions will not receive this event,
138: * it has to detect it itself. Note that this event is reported for every element even if it has
139: * no attributes, no namespaces, and no content.
140: */
141:
142: public void startContent() throws XPathException {
143: if (foundHead) {
144: foundHead = false;
145: NamePool namePool = getNamePool();
146: super .startContent();
147: int metaCode = namePool.allocate("", requiredURICode,
148: "meta");
149: super .startElement(metaCode, StandardNames.XDT_UNTYPED, 0,
150: 0);
151: int httpEquivCode = namePool.allocate("", "", "http-equiv");
152: super .attribute(httpEquivCode,
153: StandardNames.XDT_UNTYPED_ATOMIC, "Content-Type",
154: 0, 0);
155: int contentCode = namePool.allocate("", "", "content");
156: super .attribute(contentCode,
157: StandardNames.XDT_UNTYPED_ATOMIC, mediaType
158: + "; charset=" + encoding, 0, 0);
159: super .startContent();
160: droppingMetaTags = level;
161: seekingHead = false;
162: attributes = new AttributeCollectionImpl(namePool);
163: super .endElement();
164: }
165: if (!inMetaTag) {
166: super .startContent();
167: }
168: }
169:
170: /**
171: * End of element
172: */
173:
174: public void endElement() throws XPathException {
175: if (inMetaTag) {
176: inMetaTag = false;
177: // if there was an http-equiv="ContentType" attribute, discard the meta element entirely
178: boolean found = false;
179: for (int i = 0; i < attributes.getLength(); i++) {
180: String name = attributes.getLocalName(i);
181: if (comparesEqual(name, "http-equiv")) {
182: String value = attributes.getValue(i).trim();
183: if (value.equalsIgnoreCase("Content-Type")) {
184: // case-blind comparison even for XHTML
185: found = true;
186: break;
187: }
188: }
189: }
190: if (!found) {
191: // this was a meta element, but not one of the kind that we discard
192: super .startElement(metaCode, StandardNames.XDT_UNTYPED,
193: 0, 0);
194: for (int i = 0; i < attributes.getLength(); i++) {
195: int nameCode = attributes.getNameCode(i);
196: int typeCode = attributes.getTypeAnnotation(i);
197: String value = attributes.getValue(i);
198: int locationId = attributes.getLocationId(i);
199: int properties = attributes.getProperties(i);
200: super .attribute(nameCode, typeCode, value,
201: locationId, properties);
202: }
203: super .startContent();
204: super .endElement();
205: }
206: } else {
207: level--;
208: if (droppingMetaTags == level + 1) {
209: droppingMetaTags = -1;
210: }
211: super .endElement();
212: }
213: }
214:
215: }
216:
217: //
218: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
219: // you may not use this file except in compliance with the License. You may obtain a copy of the
220: // License at http://www.mozilla.org/MPL/
221: //
222: // Software distributed under the License is distributed on an "AS IS" basis,
223: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
224: // See the License for the specific language governing rights and limitations under the License.
225: //
226: // The Original Code is: all this file.
227: //
228: // The Initial Developer of the Original Code is Michael H. Kay.
229: //
230: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
231: //
232: // Contributor(s): none.
233: //
|