001: package net.sf.saxon.style;
002:
003: import net.sf.saxon.event.SaxonOutputKeys;
004: import net.sf.saxon.expr.Expression;
005: import net.sf.saxon.instruct.Executable;
006: import net.sf.saxon.om.*;
007: import net.sf.saxon.trans.SaxonErrorCode;
008: import net.sf.saxon.trans.XPathException;
009:
010: import javax.xml.transform.OutputKeys;
011: import java.util.HashMap;
012: import java.util.Iterator;
013: import java.util.Properties;
014: import java.util.StringTokenizer;
015:
016: /**
017: * An xsl:output element in the stylesheet.
018: */
019:
020: public class XSLOutput extends StyleElement {
021:
022: private int fingerprint = -1;
023: private String method = null;
024: private String version = null;
025: private String indent = null;
026: private String encoding = null;
027: private String mediaType = null;
028: private String doctypeSystem = null;
029: private String doctypePublic = null;
030: private String omitDeclaration = null;
031: private String standalone = null;
032: private String cdataElements = null;
033: private String includeContentType = null;
034: private String nextInChain = null;
035: private String representation = null;
036: private String indentSpaces = null;
037: private String byteOrderMark = null;
038: private String escapeURIAttributes = null;
039: private String normalizationForm = null;
040: private String requireWellFormed = null;
041: private String undeclareNamespaces = null;
042: private String useCharacterMaps = null;
043: private HashMap userAttributes = null;
044:
045: //Emitter handler = null;
046:
047: public void prepareAttributes() throws XPathException {
048: AttributeCollection atts = getAttributeList();
049: String nameAtt = null;
050:
051: for (int a = 0; a < atts.getLength(); a++) {
052: int nc = atts.getNameCode(a);
053: String f = getNamePool().getClarkName(nc);
054: if (f == StandardNames.NAME) {
055: nameAtt = atts.getValue(a).trim();
056: } else if (f == StandardNames.METHOD) {
057: method = atts.getValue(a).trim();
058: } else if (f == StandardNames.VERSION) {
059: version = atts.getValue(a).trim();
060: } else if (f == StandardNames.BYTE_ORDER_MARK) {
061: byteOrderMark = atts.getValue(a).trim();
062: } else if (f == StandardNames.ENCODING) {
063: encoding = atts.getValue(a).trim();
064: } else if (f == StandardNames.OMIT_XML_DECLARATION) {
065: omitDeclaration = atts.getValue(a).trim();
066: } else if (f == StandardNames.STANDALONE) {
067: standalone = atts.getValue(a).trim();
068: } else if (f == StandardNames.DOCTYPE_PUBLIC) {
069: doctypePublic = atts.getValue(a).trim();
070: } else if (f == StandardNames.DOCTYPE_SYSTEM) {
071: doctypeSystem = atts.getValue(a).trim();
072: } else if (f == StandardNames.CDATA_SECTION_ELEMENTS) {
073: cdataElements = atts.getValue(a);
074: } else if (f == StandardNames.INDENT) {
075: indent = atts.getValue(a).trim();
076: } else if (f == StandardNames.MEDIA_TYPE) {
077: mediaType = atts.getValue(a).trim();
078: } else if (f == StandardNames.INCLUDE_CONTENT_TYPE) {
079: includeContentType = atts.getValue(a).trim();
080: } else if (f == StandardNames.NORMALIZATION_FORM) {
081: normalizationForm = atts.getValue(a).trim();
082: } else if (f == StandardNames.ESCAPE_URI_ATTRIBUTES) {
083: escapeURIAttributes = atts.getValue(a).trim();
084: } else if (f == StandardNames.USE_CHARACTER_MAPS) {
085: useCharacterMaps = atts.getValue(a);
086: } else if (f == StandardNames.UNDECLARE_PREFIXES) {
087: undeclareNamespaces = atts.getValue(a);
088: } else if (f == StandardNames.SAXON_CHARACTER_REPRESENTATION) {
089: representation = atts.getValue(a).trim();
090: } else if (f == StandardNames.SAXON_INDENT_SPACES) {
091: indentSpaces = atts.getValue(a).trim();
092: } else if (f == StandardNames.SAXON_NEXT_IN_CHAIN) {
093: nextInChain = atts.getValue(a).trim();
094: } else if (f == StandardNames.SAXON_REQUIRE_WELL_FORMED) {
095: requireWellFormed = atts.getValue(a).trim();
096: } else {
097: String attributeURI = getNamePool().getURI(nc);
098: if ("".equals(attributeURI)
099: || NamespaceConstant.XSLT.equals(attributeURI)
100: || NamespaceConstant.SAXON.equals(attributeURI)) {
101: checkUnknownAttribute(nc);
102: } else {
103: String name = '{' + attributeURI + '}'
104: + atts.getLocalName(a);
105: if (userAttributes == null) {
106: userAttributes = new HashMap(5);
107: }
108: userAttributes.put(name, atts.getValue(a));
109: }
110: }
111: }
112: if (nameAtt != null) {
113: try {
114: fingerprint = makeNameCode(nameAtt.trim()) & 0xfffff;
115: } catch (NamespaceException err) {
116: compileError(err.getMessage(), "XTSE1570");
117: } catch (XPathException err) {
118: compileError(err.getMessage(), "XTSE1570");
119: }
120: }
121: }
122:
123: /**
124: * Get the name of the xsl:output declaration
125: * @return the name, as a namepool fingerprint; or -1 for an unnamed output declaration
126: */
127:
128: public int getOutputFingerprint() {
129: return fingerprint;
130: }
131:
132: public void validate() throws XPathException {
133: checkTopLevel(null);
134: checkEmpty();
135: }
136:
137: public Expression compile(Executable exec) {
138: return null;
139: }
140:
141: /**
142: * Validate the properties,
143: * and return the values as additions to a supplied Properties object.
144: */
145:
146: protected void gatherOutputProperties(Properties details,
147: HashMap precedences) throws XPathException {
148:
149: if (method != null) {
150: if (method.equals("xml") || method.equals("html")
151: || method.equals("text") || method.equals("xhtml")) {
152: checkAndPut(OutputKeys.METHOD, method, details,
153: precedences);
154: //details.put(OutputKeys.METHOD, method);
155: } else {
156: String[] parts;
157: try {
158: parts = getConfiguration().getNameChecker()
159: .getQNameParts(method);
160: String prefix = parts[0];
161: if (prefix.equals("")) {
162: compileError(
163: "method must be xml, html, xhtml, or text, or a prefixed name",
164: "XTSE1570");
165: } else {
166: String uri = getURIForPrefix(prefix, false);
167: if (uri == null) {
168: undeclaredNamespaceError(prefix, "XTSE0280");
169: }
170: checkAndPut(OutputKeys.METHOD, '{' + uri + '}'
171: + parts[1], details, precedences);
172: //details.put(OutputKeys.METHOD, '{' + uri + '}' + parts[1] );
173: }
174: } catch (QNameException e) {
175: compileError("Invalid method name. "
176: + e.getMessage(), "XTSE1570");
177: }
178: }
179: }
180:
181: if (byteOrderMark != null) {
182: if (byteOrderMark.equals("yes")
183: || byteOrderMark.equals("no")) {
184: checkAndPut(SaxonOutputKeys.BYTE_ORDER_MARK,
185: byteOrderMark, details, precedences);
186: //details.put(SaxonOutputKeys.BYTE_ORDER_MARK, byteOrderMark);
187: } else {
188: compileError(
189: "byte-order-mark value must be 'yes' or 'no'",
190: "XTSE0020");
191: }
192: }
193:
194: if (version != null) {
195: checkAndPut(OutputKeys.VERSION, version, details,
196: precedences);
197: //details.put(OutputKeys.VERSION, version);
198: }
199:
200: if (indent != null) {
201: if (indent.equals("yes") || indent.equals("no")) {
202: //details.put(OutputKeys.INDENT, indent);
203: checkAndPut(OutputKeys.INDENT, indent, details,
204: precedences);
205: } else {
206: compileError("indent value must be 'yes' or 'no'",
207: "XTSE0020");
208: }
209: }
210:
211: if (indentSpaces != null) {
212: try {
213: Integer.parseInt(indentSpaces);
214: details.put(OutputKeys.INDENT, "yes");
215: checkAndPut(SaxonOutputKeys.INDENT_SPACES,
216: indentSpaces, details, precedences);
217: //details.put(SaxonOutputKeys.INDENT_SPACES, indentSpaces);
218: } catch (NumberFormatException err) {
219: compileWarning(
220: "saxon:indent-spaces must be an integer. Using default value (3).",
221: SaxonErrorCode.SXWN9002);
222: }
223: }
224:
225: if (encoding != null) {
226: checkAndPut(OutputKeys.ENCODING, encoding, details,
227: precedences);
228: //details.put(OutputKeys.ENCODING, encoding);
229: }
230:
231: if (mediaType != null) {
232: checkAndPut(OutputKeys.MEDIA_TYPE, mediaType, details,
233: precedences);
234: //details.put(OutputKeys.MEDIA_TYPE, mediaType);
235: }
236:
237: if (doctypeSystem != null) {
238: checkAndPut(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem,
239: details, precedences);
240: //details.put(OutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
241: }
242:
243: if (doctypePublic != null) {
244: checkAndPut(OutputKeys.DOCTYPE_PUBLIC, doctypePublic,
245: details, precedences);
246: //details.put(OutputKeys.DOCTYPE_PUBLIC, doctypePublic);
247: }
248:
249: if (omitDeclaration != null) {
250: if (omitDeclaration.equals("yes")
251: || omitDeclaration.equals("no")) {
252: checkAndPut(OutputKeys.OMIT_XML_DECLARATION,
253: omitDeclaration, details, precedences);
254: //details.put(OutputKeys.OMIT_XML_DECLARATION, omitDeclaration);
255: } else {
256: compileError(
257: "omit-xml-declaration attribute must be 'yes' or 'no'",
258: "XTSE0020");
259: }
260: }
261:
262: if (standalone != null) {
263: if (standalone.equals("yes") || standalone.equals("no")
264: || standalone.equals("omit")) {
265: checkAndPut(OutputKeys.STANDALONE, standalone, details,
266: precedences);
267: //details.put(OutputKeys.STANDALONE, standalone);
268: } else {
269: compileError(
270: "standalone attribute must be 'yes' or 'no' or 'omit'",
271: "XTSE0020");
272: }
273: }
274:
275: if (cdataElements != null) {
276: String existing = details
277: .getProperty(OutputKeys.CDATA_SECTION_ELEMENTS);
278: if (existing == null) {
279: existing = "";
280: }
281: String s = "";
282: StringTokenizer st = new StringTokenizer(cdataElements);
283: while (st.hasMoreTokens()) {
284: String displayname = st.nextToken();
285: try {
286: String[] parts = getConfiguration()
287: .getNameChecker()
288: .getQNameParts(displayname);
289: String uri = getURIForPrefix(parts[0], true);
290: if (uri == null) {
291: undeclaredNamespaceError(parts[0], "XTSE0280");
292: }
293: s += " {" + uri + '}' + parts[1];
294: } catch (QNameException err) {
295: compileError("Invalid CDATA element name. "
296: + err.getMessage(), "XTSE0280");
297: }
298:
299: details.put(OutputKeys.CDATA_SECTION_ELEMENTS, existing
300: + s);
301: }
302: }
303:
304: if (normalizationForm != null
305: && !normalizationForm.equals("none")) {
306: // At this stage we check only that the normalization-form is syntactically valid,
307: // not that it is one of the supported values. The latter check is done only if we
308: // are actually serializing.
309: if (XML11Char.isXML11ValidNmtoken(normalizationForm)) {
310: // if (normalizationForm.equals("NFC") || normalizationForm.equals("NFD") ||
311: // normalizationForm.equals("NFKC") || normalizationForm.equals("NFKD") ) {
312: checkAndPut(SaxonOutputKeys.NORMALIZATION_FORM,
313: normalizationForm, details, precedences);
314: } else {
315: compileError("normalization-form must a valid NMTOKEN",
316: "XTSE0020");
317: }
318: }
319:
320: if (undeclareNamespaces != null) {
321: if (undeclareNamespaces.equals("yes")
322: || undeclareNamespaces.equals("no")) {
323: checkAndPut(SaxonOutputKeys.UNDECLARE_PREFIXES,
324: undeclareNamespaces, details, precedences);
325: //details.put(SaxonOutputKeys.UNDECLARE_PREFIXES, undeclareNamespaces);
326: } else {
327: compileError(
328: "undeclare-namespaces value must be 'yes' or 'no'",
329: "XTSE0020");
330: }
331: }
332:
333: if (useCharacterMaps != null) {
334: String s = prepareCharacterMaps(this , useCharacterMaps,
335: details);
336: details.put(SaxonOutputKeys.USE_CHARACTER_MAPS, s);
337: }
338:
339: if (representation != null) {
340: checkAndPut(SaxonOutputKeys.CHARACTER_REPRESENTATION,
341: representation, details, precedences);
342: //details.put(SaxonOutputKeys.CHARACTER_REPRESENTATION, representation);
343: }
344:
345: if (includeContentType != null) {
346: if (includeContentType.equals("yes")
347: || includeContentType.equals("no")) {
348: checkAndPut(SaxonOutputKeys.INCLUDE_CONTENT_TYPE,
349: includeContentType, details, precedences);
350: //details.put(SaxonOutputKeys.INCLUDE_CONTENT_TYPE, includeContentType);
351: } else {
352: compileError(
353: "include-content-type attribute must be 'yes' or 'no'",
354: "XTSE0020");
355: }
356: }
357:
358: if (escapeURIAttributes != null) {
359: if (escapeURIAttributes.equals("yes")
360: || escapeURIAttributes.equals("no")) {
361: checkAndPut(SaxonOutputKeys.ESCAPE_URI_ATTRIBUTES,
362: escapeURIAttributes, details, precedences);
363: //details.put(SaxonOutputKeys.ESCAPE_URI_ATTRIBUTES, escapeURIAttributes);
364: } else {
365: compileError(
366: "escape-uri-attributes value must be 'yes' or 'no'",
367: "XTSE0020");
368: }
369: }
370:
371: if (nextInChain != null) {
372: checkAndPut(SaxonOutputKeys.NEXT_IN_CHAIN, nextInChain,
373: details, precedences);
374: checkAndPut(SaxonOutputKeys.NEXT_IN_CHAIN_BASE_URI,
375: getSystemId(), details, precedences);
376: //details.put(SaxonOutputKeys.NEXT_IN_CHAIN, nextInChain);
377: //details.put(SaxonOutputKeys.NEXT_IN_CHAIN_BASE_URI, getSystemId());
378: }
379:
380: if (requireWellFormed != null) {
381: if (requireWellFormed.equals("yes")
382: || requireWellFormed.equals("no")) {
383: checkAndPut(SaxonOutputKeys.REQUIRE_WELL_FORMED,
384: requireWellFormed, details, precedences);
385: //details.put(SaxonOutputKeys.REQUIRE_WELL_FORMED, requireWellFormed);
386: } else {
387: compileWarning(
388: "saxon:require-well-formed value must be 'yes' or 'no' (treated as no)",
389: SaxonErrorCode.SXWN9003);
390: }
391: }
392:
393: // deal with user-defined attributes
394:
395: if (userAttributes != null) {
396: Iterator iter = userAttributes.keySet().iterator();
397: while (iter.hasNext()) {
398: String attName = (String) iter.next();
399: String data = (String) userAttributes.get(attName);
400: details.put(attName, data);
401: }
402: }
403:
404: }
405:
406: /**
407: * Add an output property to the list of properties after checking that it is consistent
408: * with other properties
409: */
410:
411: public void checkAndPut(String property, String value,
412: Properties props, HashMap precedences)
413: throws XPathException {
414: String old = props.getProperty(property);
415: if (old == null) {
416: props.setProperty(property, value);
417: precedences.put(property, new Integer(getPrecedence()));
418: } else if (old.equals(value)) {
419: // do nothing
420: } else {
421: Integer oldPrec = (Integer) precedences.get(property);
422: if (oldPrec == null)
423: return; // shouldn't happen but ignore it
424: int op = oldPrec.intValue();
425: if (op > getPrecedence()) {
426: // ignore this value, the other has higher precedence
427: } else if (op == getPrecedence()) {
428: compileError("Conflicting values for output property "
429: + property, "XTSE1560");
430: } else {
431: // this has higher precedence: can't happen
432: throw new IllegalStateException(
433: "Output properties must be processed in decreasing precedence order");
434: }
435: }
436: }
437:
438: /**
439: * Process the use-character-maps attribute
440: * @param details The existing output properties
441: * @return the augmented value of the use-character-maps attribute in Clark notation
442: * @throws XPathException if the value is invalid
443: */
444: public static String prepareCharacterMaps(StyleElement element,
445: String useCharacterMaps, Properties details)
446: throws XPathException {
447: XSLStylesheet principal = element.getPrincipalStylesheet();
448: String existing = details
449: .getProperty(SaxonOutputKeys.USE_CHARACTER_MAPS);
450: if (existing == null) {
451: existing = "";
452: }
453: String s = "";
454: StringTokenizer st = new StringTokenizer(useCharacterMaps);
455: while (st.hasMoreTokens()) {
456: String displayname = st.nextToken();
457: try {
458: String[] parts = element.getConfiguration()
459: .getNameChecker().getQNameParts(displayname);
460: String uri = element.getURIForPrefix(parts[0], false);
461: if (uri == null) {
462: element
463: .undeclaredNamespaceError(parts[0],
464: "XT0280");
465: }
466: int nameCode = element.getTargetNamePool().allocate(
467: parts[0], uri, parts[1]);
468: XSLCharacterMap ref = principal
469: .getCharacterMap(nameCode & 0xfffff);
470: if (ref == null) {
471: element.compileError("No character-map named '"
472: + displayname + "' has been defined",
473: "XTSE1590");
474: }
475: s += " {" + uri + '}' + parts[1];
476: } catch (QNameException err) {
477: element.compileError("Invalid character-map name. "
478: + err.getMessage(), "XTSE1590");
479: }
480:
481: }
482: existing = s + existing;
483: return existing;
484: }
485:
486: }
487:
488: //
489: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
490: // you may not use this file except in compliance with the License. You may obtain a copy of the
491: // License at http://www.mozilla.org/MPL/
492: //
493: // Software distributed under the License is distributed on an "AS IS" basis,
494: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
495: // See the License for the specific language governing rights and limitations under the License.
496: //
497: // The Original Code is: all this file.
498: //
499: // The Initial Developer of the Original Code is Michael H. Kay.
500: //
501: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
502: //
503: // Contributor(s): none.
504: //
|