0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.xsl.grammar;
0043:
0044: import java.io.IOException;
0045: import java.util.*;
0046: import javax.swing.Icon; //import org.apache.xpath.XPathAPI;
0047:
0048: import org.netbeans.api.xml.services.UserCatalog;
0049: import org.netbeans.modules.xml.api.model.*;
0050: import org.netbeans.modules.xml.spi.dom.*;
0051: import org.netbeans.modules.xsl.api.XSLCustomizer;
0052: import org.openide.loaders.DataObject;
0053: import org.openide.nodes.PropertySupport;
0054: import org.openide.util.Lookup;
0055: import org.openide.util.NbBundle;
0056: import org.openide.util.lookup.Lookups;
0057:
0058: import org.w3c.dom.*;
0059: import org.xml.sax.EntityResolver;
0060: import org.xml.sax.InputSource;
0061: import org.xml.sax.SAXException;
0062:
0063: /**
0064: * This class implements code completion for XSL transformation files.
0065: * XSL elements in the completion are hardcoded from the XSLT spec, but the
0066: * result elements are gathered from the "doctype-public" and "doctype-system"
0067: * attributes of the xsl:output element.
0068: *
0069: * @author asgeir@dimonsoftware.com
0070: */
0071: public final class XSLGrammarQuery implements GrammarQuery {
0072:
0073: private DataObject dataObject;
0074:
0075: /** Contains a mapping from XSL namespace element names to set of names of
0076: * allowed XSL children. Neither the element name keys nor the names in the
0077: * value set should contain the namespace prefix.
0078: */
0079: private static Map elementDecls;
0080:
0081: /** Contains a mapping from XSL namespace element names to set of names of
0082: * allowed XSL attributes for that element. The element name keys should
0083: * not contain the namespace prefix.
0084: */
0085: private static Map attrDecls;
0086:
0087: /** A Set of XSL attributes which should be allowd for result elements*/
0088: private static Set resultElementAttr;
0089:
0090: /** An object which indicates that result element should be allowed in a element Set */
0091: private static String resultElements = "RESULT_ELEMENTS_DUMMY_STRING"; // NOI18N
0092:
0093: /** A Set of elements which should be allowed at template level in XSL stylesheet */
0094: private static Set template;
0095:
0096: /** Contains a mapping from XSL namespace element names to an attribute name which
0097: * should contain XPath expression. The element name keys should
0098: * not contain the namespace prefix.
0099: */
0100: private static Map exprAttributes;
0101:
0102: /** A set containing all functions allowed in XSLT */
0103: private static Set xslFunctions;
0104:
0105: /** A set containing XPath axes */
0106: private static Set xpathAxes;
0107:
0108: /** A list of prefixes using the "http://www.w3.org/1999/XSL/Transform" namespace
0109: * defined in the context XSL document. The first prefix in the list is the actual XSL
0110: * transformation prefix, which is normally defined on the xsl:stylesheet element.
0111: */
0112: private List prefixList = new LinkedList();
0113:
0114: /** A GrammarQuery for the result elements created for the doctype-public" and
0115: * "doctype-system" attributes of the xsl:output element.*/
0116: private GrammarQuery resultGrammarQuery;
0117:
0118: /** The value of the system identifier of the DTD which was used when
0119: * resultGrammarQuery was previously created */
0120: private String lastDoctypeSystem;
0121:
0122: /** The value of the public identifier of the DTD which was used when
0123: * resultGrammarQuery was previously created */
0124: private String lastDoctypePublic;
0125:
0126: // we cannot parse SGML DTD for HTML, let emulate it by XHTML DTD
0127: private final static String XHTML_PUBLIC_ID = System.getProperty(
0128: "netbeans.xsl.html.public",
0129: "-//W3C//DTD XHTML 1.0 Transitional//EN"); // NOI18N
0130:
0131: // we cannot parse SGML DTD for HTML, let emulate it by XHTML DTD
0132: private final static String XHTML_SYSTEM_ID = System.getProperty(
0133: "netbeans.xsl.html.system",
0134: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"); // NOI18N
0135:
0136: // namespace that this grammar supports
0137: public static final String XSLT_NAMESPACE_URI = "http://www.w3.org/1999/XSL/Transform"; // NOI18N
0138:
0139: /** Folder which stores instances of custom external XSL customizers */
0140: private static final String CUSTOMIZER_FOLDER = "Plugins/XML/XSLCustomizer"; // NOI18N
0141:
0142: private XSLCustomizer customizer = null;
0143:
0144: private ResourceBundle bundle = NbBundle
0145: .getBundle(XSLGrammarQuery.class);
0146:
0147: /** Creates a new instance of XSLGrammarQuery */
0148: public XSLGrammarQuery(DataObject dataObject) {
0149: this .dataObject = dataObject;
0150: }
0151:
0152: //////////////////////////////////////////7
0153: // Getters for the static members
0154:
0155: private static Map getElementDecls() {
0156: if (elementDecls == null) {
0157: elementDecls = new HashMap();
0158: attrDecls = new HashMap();
0159:
0160: // Commonly used variables
0161: Set emptySet = new TreeSet();
0162: String spaceAtt = "xml:space"; // NOI18N
0163: Set tmpSet;
0164:
0165: ////////////////////////////////////////////////
0166: // Initialize common sets
0167:
0168: Set charInstructions = new TreeSet(Arrays
0169: .asList(new String[] {
0170: "apply-templates", // NOI18N
0171: "call-template", "apply-imports",
0172: "for-each",
0173: "value-of", // NOI18N
0174: "copy-of", "number", "choose", "if",
0175: "text", "copy", // NOI18N
0176: "variable", "message", "fallback" })); // NOI18N
0177:
0178: Set instructions = new TreeSet(charInstructions);
0179: instructions.addAll(Arrays.asList(new String[] {
0180: "processing-instruction", // NOI18N
0181: "comment", "element", "attribute" })); // NOI18N
0182:
0183: Set charTemplate = charInstructions; // We don't care about PCDATA
0184:
0185: template = new TreeSet(instructions);
0186: template.add(resultElements);
0187:
0188: Set topLevel = new TreeSet(Arrays
0189: .asList(new String[] {
0190: "import",
0191: "include",
0192: "strip-space", // NOI18N
0193: "preserve-space", "output", "key",
0194: "decimal-format",
0195: "attribute-set", // NOI18N
0196: "variable", "param", "template",
0197: "namespace-alias" })); // NOI18N
0198:
0199: Set topLevelAttr = new TreeSet(Arrays.asList(new String[] {
0200: "extension-element-prefixes", // NOI18N
0201: "exclude-result-prefixes", "id", "version",
0202: spaceAtt })); // NOI18N
0203:
0204: resultElementAttr = new TreeSet(Arrays.asList(new String[] {
0205: "extension-element-prefixes", // NOI18N
0206: "exclude-result-prefixes", "use-attribute-sets",
0207: "version" })); // NOI18N
0208:
0209: ////////////////////////////////////////////////
0210: // Add items to elementDecls and attrDecls maps
0211:
0212: // xsl:stylesheet
0213: elementDecls.put("stylesheet", topLevel); // NOI18N
0214: attrDecls.put("stylesheet", topLevelAttr); // NOI18N
0215:
0216: // xsl:transform
0217: elementDecls.put("transform", topLevel); // NOI18N
0218: attrDecls.put("transform", topLevelAttr); // NOI18N
0219:
0220: // xsl:import
0221: elementDecls.put("import", emptySet); // NOI18N
0222: attrDecls.put("import", new TreeSet(Arrays
0223: .asList(new String[] { "href" }))); // NOI18N
0224:
0225: // xxsl:include
0226: elementDecls.put("include", emptySet); // NOI18N
0227: attrDecls.put("include", new TreeSet(Arrays
0228: .asList(new String[] { "href" }))); // NOI18N
0229:
0230: // xsl:strip-space
0231: elementDecls.put("strip-space", emptySet); // NOI18N
0232: attrDecls.put("strip-space", new TreeSet(Arrays
0233: .asList(new String[] { "elements" }))); // NOI18N
0234:
0235: // xsl:preserve-space
0236: elementDecls.put("preserve-space", emptySet); // NOI18N
0237: attrDecls.put("preserve-space", new TreeSet(Arrays
0238: .asList(new String[] { "elements" }))); // NOI18N
0239:
0240: // xsl:output
0241: elementDecls.put("output", emptySet); // NOI18N
0242: attrDecls.put("output", new TreeSet(Arrays
0243: .asList(new String[] {
0244: "method", // NOI18N
0245: "version", "encoding",
0246: "omit-xml-declaration",
0247: "standalone",
0248: "doctype-public", // NOI18N
0249: "doctype-system", "cdata-section-elements",
0250: "indent", "media-type" }))); // NOI18N
0251:
0252: // xsl:key
0253: elementDecls.put("key", emptySet); // NOI18N
0254: attrDecls.put("key", new TreeSet(Arrays
0255: .asList(new String[] { "name", "match", "use" }))); // NOI18N
0256:
0257: // xsl:decimal-format
0258: elementDecls.put("decimal-format", emptySet); // NOI18N
0259: attrDecls.put("decimal-format", new TreeSet(Arrays
0260: .asList(new String[] {
0261: "name", // NOI18N
0262: "decimal-separator", "grouping-separator",
0263: "infinity", "minus-sign",
0264: "NaN", // NOI18N
0265: "percent", "per-mille", "zero-digit",
0266: "digit", "pattern-separator" }))); // NOI18N
0267:
0268: // xsl:namespace-alias
0269: elementDecls.put("namespace-alias", emptySet); // NOI18N
0270: attrDecls.put("namespace-alias", new TreeSet(Arrays
0271: .asList(new String[] { // NOI18N
0272: "stylesheet-prefix", "result-prefix" }))); // NOI18N
0273:
0274: // xsl:template
0275: tmpSet = new TreeSet(instructions);
0276: tmpSet.add(resultElements);
0277: tmpSet.add("param"); // NOI18N
0278: elementDecls.put("template", tmpSet); // NOI18N
0279: attrDecls.put("template", new TreeSet(Arrays
0280: .asList(new String[] { // NOI18N
0281: "match", "name", "priority", "mode", spaceAtt }))); // NOI18N
0282:
0283: // xsl:value-of
0284: elementDecls.put("value-of", emptySet); // NOI18N
0285: attrDecls.put("value-of", new TreeSet(Arrays
0286: .asList(new String[] { // NOI18N
0287: "select", "disable-output-escaping" }))); // NOI18N
0288:
0289: // xsl:copy-of
0290: elementDecls.put("copy-of", emptySet); // NOI18N
0291: attrDecls.put("copy-of", new TreeSet(Arrays
0292: .asList(new String[] { "select" }))); // NOI18N
0293:
0294: // xsl:number
0295: elementDecls.put("number", emptySet); // NOI18N
0296: attrDecls.put("number", new TreeSet(Arrays
0297: .asList(new String[] { // NOI18N
0298: "level", "count", "from", "value", "format",
0299: "lang", "letter-value", // NOI18N
0300: "grouping-separator", "grouping-size" }))); // NOI18N
0301:
0302: // xsl:apply-templates
0303: elementDecls.put("apply-templates", new TreeSet(Arrays
0304: .asList(new String[] { // NOI18N
0305: "sort", "with-param" }))); // NOI18N
0306: attrDecls.put("apply-templates", new TreeSet(Arrays
0307: .asList(new String[] { // NOI18N
0308: "select", "mode" }))); // NOI18N
0309:
0310: // xsl:apply-imports
0311: elementDecls.put("apply-imports", emptySet); // NOI18N
0312: attrDecls.put("apply-imports", emptySet); // NOI18N
0313:
0314: // xsl:for-each
0315: tmpSet = new TreeSet(instructions);
0316: tmpSet.add(resultElements);
0317: tmpSet.add("sort"); // NOI18N
0318: elementDecls.put("for-each", tmpSet); // NOI18N
0319: attrDecls.put("for-each", new TreeSet(Arrays
0320: .asList(new String[] { // NOI18N
0321: "select", spaceAtt }))); // NOI18N
0322:
0323: // xsl:sort
0324: elementDecls.put("sort", emptySet); // NOI18N
0325: attrDecls.put("sort", new TreeSet(Arrays
0326: .asList(new String[] { // NOI18N
0327: "select", "lang", "data-type", "order",
0328: "case-order" }))); // NOI18N
0329:
0330: // xsl:if
0331: elementDecls.put("if", template); // NOI18N
0332: attrDecls.put("if", new TreeSet(Arrays.asList(new String[] {
0333: "test", spaceAtt }))); // NOI18N
0334:
0335: // xsl:choose
0336: elementDecls.put("choose", new TreeSet(Arrays
0337: .asList(new String[] { // NOI18N
0338: "when", "otherwise" }))); // NOI18N
0339: attrDecls.put("choose", new TreeSet(Arrays
0340: .asList(new String[] { spaceAtt }))); // NOI18N
0341:
0342: // xsl:when
0343: elementDecls.put("when", template); // NOI18N
0344: attrDecls.put("when", new TreeSet(Arrays
0345: .asList(new String[] { // NOI18N
0346: "test", spaceAtt }))); // NOI18N
0347:
0348: // xsl:otherwise
0349: elementDecls.put("otherwise", template); // NOI18N
0350: attrDecls.put("otherwise", new TreeSet(Arrays
0351: .asList(new String[] { spaceAtt }))); // NOI18N
0352:
0353: // xsl:attribute-set
0354: elementDecls.put("sort", new TreeSet(Arrays
0355: .asList(new String[] { "attribute" }))); // NOI18N
0356: attrDecls.put("attribute-set", new TreeSet(Arrays
0357: .asList(new String[] { // NOI18N
0358: "name", "use-attribute-sets" }))); // NOI18N
0359:
0360: // xsl:call-template
0361: elementDecls.put("call-template", new TreeSet(Arrays
0362: .asList(new String[] { "with-param" }))); // NOI18N
0363: attrDecls.put("call-template", new TreeSet(Arrays
0364: .asList(new String[] { "name" }))); // NOI18N
0365:
0366: // xsl:with-param
0367: elementDecls.put("with-param", template); // NOI18N
0368: attrDecls.put("with-param", new TreeSet(Arrays
0369: .asList(new String[] { // NOI18N
0370: "name", "select" }))); // NOI18N
0371:
0372: // xsl:variable
0373: elementDecls.put("variable", template); // NOI18N
0374: attrDecls.put("variable", new TreeSet(Arrays
0375: .asList(new String[] { // NOI18N
0376: "name", "select" }))); // NOI18N
0377:
0378: // xsl:param
0379: elementDecls.put("param", template); // NOI18N
0380: attrDecls.put("param", new TreeSet(Arrays
0381: .asList(new String[] { // NOI18N
0382: "name", "select" }))); // NOI18N
0383:
0384: // xsl:text
0385: elementDecls.put("text", emptySet); // NOI18N
0386: attrDecls.put("text", new TreeSet(Arrays
0387: .asList(new String[] { // NOI18N
0388: "disable-output-escaping" }))); // NOI18N
0389:
0390: // xsl:processing-instruction
0391: elementDecls.put("processing-instruction", charTemplate); // NOI18N
0392: attrDecls.put("processing-instruction", new TreeSet(Arrays
0393: .asList(new String[] { // NOI18N
0394: "name", spaceAtt }))); // NOI18N
0395:
0396: // xsl:element
0397: elementDecls.put("element", template); // NOI18N
0398: attrDecls.put("element", new TreeSet(
0399: Arrays.asList(new String[] { // NOI18N
0400: "name", "namespace", "use-attribute-sets",
0401: spaceAtt }))); // NOI18N
0402:
0403: // xsl:attribute
0404: elementDecls.put("attribute", charTemplate); // NOI18N
0405: attrDecls.put("attribute", new TreeSet(Arrays
0406: .asList(new String[] { // NOI18N
0407: "name", "namespace", spaceAtt }))); // NOI18N
0408:
0409: // xsl:comment
0410: elementDecls.put("comment", charTemplate); // NOI18N
0411: attrDecls.put("comment", new TreeSet(Arrays
0412: .asList(new String[] { spaceAtt }))); // NOI18N
0413:
0414: // xsl:copy
0415: elementDecls.put("copy", template); // NOI18N
0416: attrDecls.put("copy", new TreeSet(Arrays
0417: .asList(new String[] { // NOI18N
0418: spaceAtt, "use-attribute-sets" }))); // NOI18N
0419:
0420: // xsl:message
0421: elementDecls.put("message", template); // NOI18N
0422: attrDecls.put("message", new TreeSet(Arrays
0423: .asList(new String[] { // NOI18N
0424: spaceAtt, "terminate" }))); // NOI18N
0425:
0426: // xsl:fallback
0427: elementDecls.put("fallback", template); // NOI18N
0428: attrDecls.put("fallback", new TreeSet(Arrays
0429: .asList(new String[] { spaceAtt }))); // NOI18N
0430: }
0431: return elementDecls;
0432: }
0433:
0434: private static Map getAttrDecls() {
0435: if (attrDecls == null) {
0436: getElementDecls();
0437: }
0438: return attrDecls;
0439: }
0440:
0441: private static Set getResultElementAttr() {
0442: if (resultElementAttr == null) {
0443: getElementDecls();
0444: }
0445: return resultElementAttr;
0446: }
0447:
0448: private static Set getTemplate() {
0449: if (template == null) {
0450: getElementDecls();
0451: }
0452: return template;
0453: }
0454:
0455: private static Set getXslFunctions() {
0456: if (xslFunctions == null) {
0457: xslFunctions = new TreeSet(Arrays.asList(new String[] {
0458: "boolean(",
0459: "ceiling(",
0460: "concat(",
0461: "contains(",
0462: "count(",
0463: "current()",
0464: "document(", // NOI18N
0465: "false()",
0466: "floor(",
0467: "format-number(",
0468: "generate-id(", // NOI18N
0469: "id(", "local-name(", "key(", "lang(",
0470: "last()",
0471: "name(",
0472: "namespace-uri(",
0473: "normalize-space(", // NOI18N
0474: "not(", "number(", "position()",
0475: "round(",
0476: "starts-with(",
0477: "string(", // NOI18N
0478: "string-length(", "substring(", "substring-after(",
0479: "substring-before(",
0480: "sum(", // NOI18N
0481: "system-property(", "translate(", "true()",
0482: "unparsed-entity-uri(" })); // NOI18N
0483: }
0484: return xslFunctions;
0485: }
0486:
0487: private static Set getXPathAxes() {
0488: if (xpathAxes == null) {
0489: xpathAxes = new TreeSet(Arrays.asList(new String[] {
0490: "ancestor::",
0491: "ancestor-or-self::", // NOI18N
0492: "attribute::", "child::", "descendant::",
0493: "descendant-or-self::",
0494: "following::", // NOI18N
0495: "following-sibling::", "namespace::", "parent::",
0496: "preceding::", // NOI18N
0497: "preceding-sibling::", "self::" })); // NOI18N
0498: }
0499: return xpathAxes;
0500: }
0501:
0502: private static Map getExprAttributes() {
0503: if (exprAttributes == null) {
0504: exprAttributes = new HashMap();
0505: exprAttributes.put("key", "use"); // NOI18N
0506: exprAttributes.put("value-of", "select"); // NOI18N
0507: exprAttributes.put("copy-of", "select"); // NOI18N
0508: exprAttributes.put("number", "value"); // NOI18N
0509: //??? what about match one
0510: exprAttributes.put("apply-templates", "select"); // NOI18N
0511: exprAttributes.put("for-each", "select"); // NOI18N
0512: exprAttributes.put("sort", "select"); // NOI18N
0513: exprAttributes.put("if", "test"); // NOI18N
0514: exprAttributes.put("when", "test"); // NOI18N
0515: exprAttributes.put("with-param", "select"); // NOI18N
0516: exprAttributes.put("variable", "select"); // NOI18N
0517: exprAttributes.put("param", "select"); // NOI18N
0518: }
0519: return exprAttributes;
0520: }
0521:
0522: ////////////////////////////////////////////////////////////////////////////////
0523: // GrammarQuery interface fulfillment
0524:
0525: /**
0526: * Support completions of elements defined by XSLT spec and by the <output>
0527: * doctype attribute (in result space).
0528: */
0529: public Enumeration queryElements(HintContext ctx) {
0530: Node node = ((Node) ctx).getParentNode();
0531:
0532: String prefix = ctx.getCurrentPrefix();
0533: QueueEnumeration list = new QueueEnumeration();
0534:
0535: if (node instanceof Element) {
0536: Element el = (Element) node;
0537: updateProperties(el);
0538: if (prefixList.size() == 0)
0539: return org.openide.util.Enumerations.empty();
0540:
0541: String firstXslPrefixWithColon = prefixList.get(0) + ":"; // NOI18N
0542: Set elements;
0543: if (el.getTagName().startsWith(firstXslPrefixWithColon)) {
0544: String parentNCName = el.getTagName().substring(
0545: firstXslPrefixWithColon.length());
0546: elements = (Set) getElementDecls().get(parentNCName);
0547: } else {
0548: // Children of result elements should always be the template set
0549: elements = getTemplate();
0550: }
0551:
0552: // First we add the Result elements
0553: if (elements != null && resultGrammarQuery != null
0554: && elements.contains(resultElements)) {
0555: ResultHintContext resultHintContext = new ResultHintContext(
0556: ctx, firstXslPrefixWithColon, null);
0557: Enumeration resultEnum = resultGrammarQuery
0558: .queryElements(resultHintContext);
0559: while (resultEnum.hasMoreElements()) {
0560: list.put(resultEnum.nextElement());
0561: }
0562: }
0563:
0564: // Then we add the XSLT elements of the first prefix (normally of the stylesheet node).
0565: addXslElementsToEnum(list, elements, prefixList.get(0)
0566: + ":", prefix); // NOI18N
0567:
0568: // Finally we add xsl namespace elements with other prefixes than the first one
0569: for (int prefixInd = 1; prefixInd < prefixList.size(); prefixInd++) {
0570: String curPrefix = (String) prefixList.get(prefixInd)
0571: + ":"; // NOI18N
0572: Node curNode = el;
0573: String curName = null;
0574: while (curNode != null
0575: && null != (curName = curNode.getNodeName())
0576: && !curName.startsWith(curPrefix)) {
0577: curNode = curNode.getParentNode();
0578: }
0579:
0580: if (curName == null) {
0581: // This must be the document node
0582: addXslElementsToEnum(list, getElementDecls()
0583: .keySet(), curPrefix, prefix);
0584: } else {
0585: String parentName = curName.substring(curPrefix
0586: .length());
0587: elements = (Set) getElementDecls().get(parentName);
0588: addXslElementsToEnum(list, elements, curPrefix,
0589: prefix);
0590: }
0591: }
0592:
0593: } else if (node instanceof Document) {
0594: //??? it should be probably only root element name
0595: if (prefixList.size() == 0)
0596: return org.openide.util.Enumerations.empty();
0597: addXslElementsToEnum(list, getElementDecls().keySet(),
0598: prefixList.get(0) + ":", prefix); // NOI18N
0599: } else {
0600: return org.openide.util.Enumerations.empty();
0601: }
0602:
0603: return list;
0604: }
0605:
0606: public Enumeration queryAttributes(HintContext ctx) {
0607: Element el = null;
0608: // Support two versions of GrammarQuery contract
0609: if (ctx.getNodeType() == Node.ATTRIBUTE_NODE) {
0610: el = ((Attr) ctx).getOwnerElement();
0611: } else if (ctx.getNodeType() == Node.ELEMENT_NODE) {
0612: el = (Element) ctx;
0613: }
0614: if (el == null)
0615: return org.openide.util.Enumerations.empty();
0616:
0617: String elTagName = el.getTagName();
0618: NamedNodeMap existingAttributes = el.getAttributes();
0619:
0620: updateProperties(el);
0621:
0622: String curXslPrefix = null;
0623: for (int ind = 0; ind < prefixList.size(); ind++) {
0624: if (elTagName
0625: .startsWith((String) prefixList.get(ind) + ":")) { // NOI18N
0626: curXslPrefix = (String) prefixList.get(ind) + ":"; // NOI18N
0627: break;
0628: }
0629: }
0630:
0631: Set possibleAttributes;
0632: if (curXslPrefix != null) {
0633: // Attributes of XSL element
0634: possibleAttributes = (Set) getAttrDecls().get(
0635: el.getTagName().substring(curXslPrefix.length()));
0636: } else {
0637: // XSL Attributes of Result element
0638: possibleAttributes = new TreeSet();
0639: if (prefixList.size() > 0) {
0640: Iterator it = getResultElementAttr().iterator();
0641: while (it.hasNext()) {
0642: possibleAttributes.add((String) prefixList.get(0)
0643: + ":" + (String) it.next()); // NOI18N
0644: }
0645: }
0646: }
0647: if (possibleAttributes == null)
0648: return org.openide.util.Enumerations.empty();
0649:
0650: String prefix = ctx.getCurrentPrefix();
0651:
0652: QueueEnumeration list = new QueueEnumeration();
0653:
0654: if (resultGrammarQuery != null) {
0655: Enumeration enum2 = resultGrammarQuery.queryAttributes(ctx);
0656: while (enum2.hasMoreElements()) {
0657: GrammarResult resNode = (GrammarResult) enum2
0658: .nextElement();
0659: if (!possibleAttributes.contains(resNode.getNodeName())) {
0660: list.put(resNode);
0661: }
0662: }
0663: }
0664:
0665: Iterator it = possibleAttributes.iterator();
0666: while (it.hasNext()) {
0667: String next = (String) it.next();
0668: if (next.startsWith(prefix)) {
0669: if (existingAttributes.getNamedItem(next) == null) {
0670: list.put(new MyAttr(next));
0671: }
0672: }
0673: }
0674:
0675: return list;
0676: }
0677:
0678: public Enumeration queryValues(HintContext ctx) {
0679: if (ctx.getNodeType() == Node.ATTRIBUTE_NODE) {
0680: updateProperties(((Attr) ctx).getOwnerElement());
0681: if (prefixList.size() == 0)
0682: return org.openide.util.Enumerations.empty();
0683: String xslNamespacePrefix = prefixList.get(0) + ":"; // NOI18N
0684:
0685: String prefix = ctx.getCurrentPrefix();
0686:
0687: Attr attr = (Attr) ctx;
0688:
0689: boolean isXPath = false;
0690: String elName = attr.getOwnerElement().getNodeName();
0691: if (elName.startsWith(xslNamespacePrefix)) {
0692: String key = elName.substring(xslNamespacePrefix
0693: .length());
0694: String xpathAttrName = (String) getExprAttributes()
0695: .get(key);
0696: if (xpathAttrName != null
0697: && xpathAttrName.equals(attr.getNodeName())) {
0698: // This is an XSLT element which should contain XPathExpression
0699: isXPath = true;
0700: }
0701:
0702: // consult awailable public IDs with users catalog
0703: if ("output".equals(key)) { // NOI18N
0704: if ("doctype-public".equals(attr.getName())) { // NOI18N
0705: UserCatalog catalog = UserCatalog.getDefault();
0706: if (catalog == null)
0707: return org.openide.util.Enumerations
0708: .empty();
0709: QueueEnumeration en = new QueueEnumeration();
0710: Iterator it = catalog.getPublicIDs();
0711: while (it.hasNext()) {
0712: String next = (String) it.next();
0713: if (next != null && next.startsWith(prefix)) {
0714: en.put(new MyText(next));
0715: }
0716: }
0717: return en;
0718: }
0719: }
0720: }
0721:
0722: String preExpression = ""; // NOI18N
0723:
0724: if (!isXPath) {
0725: // Check if we are inside { } for attribute value
0726: String nodeValue = attr.getNodeValue();
0727: int exprStart = nodeValue.lastIndexOf('{', prefix
0728: .length() - 1); // NOI18N
0729: int exprEnd = nodeValue.indexOf('}', prefix.length()); // NOI18N
0730: //Util.THIS.debug("exprStart: " + exprStart); // NOI18N
0731: //Util.THIS.debug("exprEnd: " + exprEnd); // NOI18N
0732: if (exprStart != -1 && exprEnd != -1) {
0733: isXPath = true;
0734: preExpression = prefix.substring(0, exprStart + 1);
0735: prefix = prefix.substring(exprStart + 1);
0736: }
0737:
0738: }
0739:
0740: if (isXPath) {
0741: // This is an XPath expression
0742: QueueEnumeration list = new QueueEnumeration();
0743:
0744: int curIndex = prefix.length();
0745: while (curIndex > 0) {
0746: curIndex--;
0747: char curChar = prefix.charAt(curIndex);
0748: if (curChar == '(' || curChar == ','
0749: || curChar == ' ') { // NOI18N
0750: curIndex++;
0751: break;
0752: }
0753: }
0754:
0755: preExpression += prefix.substring(0, curIndex);
0756: String subExpression = prefix.substring(curIndex);
0757:
0758: int lastDiv = subExpression.lastIndexOf('/'); // NOI18N
0759: String subPre = ""; // NOI18N
0760: String subRest = ""; // NOI18N
0761: if (lastDiv != -1) {
0762: subPre = subExpression.substring(0, lastDiv + 1);
0763: subRest = subExpression.substring(lastDiv + 1);
0764: } else {
0765: subRest = subExpression;
0766: }
0767:
0768: // At this point we need to consult transformed document or
0769: // its grammar.
0770: // [93792] +
0771: // Object selScenarioObj = scenarioCookie.getModel().getSelectedItem();
0772: // [93792] -
0773: /*
0774: if (selScenarioObj instanceof XSLScenario) {
0775: XSLScenario scenario = (XSLScenario)selScenarioObj;
0776: Document doc = null;
0777: try {
0778: doc = scenario.getSourceDocument(dataObject, false);
0779: } catch(Exception e) {
0780: // We don't care, ignore
0781: }
0782:
0783: if (doc != null) {
0784: Element docElement = doc.getDocumentElement();
0785:
0786: Set childNodeNames = new TreeSet();
0787:
0788: String combinedXPath;
0789: if (subPre.startsWith("/")) { // NOI18N
0790: // This is an absolute XPath
0791: combinedXPath = subPre;
0792: } else {
0793: // This is a relative XPath
0794:
0795: // Traverse up the documents tree looking for xsl:for-each
0796: String xslForEachName = xslNamespacePrefix + "for-each"; // NOI18N
0797: List selectAttrs = new LinkedList();
0798: Node curNode = attr.getOwnerElement();
0799: if (curNode != null) {
0800: // We don't want to add select of our selfs
0801: curNode = curNode.getParentNode();
0802: }
0803:
0804: while (curNode != null && !(curNode instanceof Document)) {
0805: if (curNode.getNodeName().equals(xslForEachName)) {
0806: selectAttrs.add(0, ((Element)curNode).getAttribute("select")); // NOI18N
0807: }
0808:
0809: curNode = curNode.getParentNode();
0810: }
0811:
0812: combinedXPath = ""; // NOI18N
0813: for (int ind = 0; ind < selectAttrs.size(); ind++) {
0814: combinedXPath += selectAttrs.get(ind) + "/"; // NOI18N
0815: }
0816: combinedXPath += subPre;
0817: }
0818:
0819: try {
0820: NodeList nodeList = XPathAPI.selectNodeList(doc, combinedXPath + "child::*"); // NOI18N
0821: for (int ind = 0; ind < nodeList.getLength(); ind++) {
0822: Node curResNode = nodeList.item(ind);
0823: childNodeNames.add(curResNode.getNodeName());
0824: }
0825:
0826: nodeList = XPathAPI.selectNodeList(doc, combinedXPath + "@*"); // NOI18N
0827: for (int ind = 0; ind < nodeList.getLength(); ind++) {
0828: Node curResNode = nodeList.item(ind);
0829: childNodeNames.add("@" + curResNode.getNodeName()); // NOI18N
0830: }
0831: } catch (Exception e) {
0832: Util.THIS.debug("Ignored during XPathAPI operations", e); // NOI18N
0833: // We don't care, ignore
0834: }
0835:
0836: addItemsToEnum(list, childNodeNames, subRest, preExpression + subPre);
0837: }
0838: }*/
0839:
0840: addItemsToEnum(list, getXPathAxes(), subRest,
0841: preExpression + subPre);
0842: addItemsToEnum(list, getXslFunctions(), subExpression,
0843: preExpression);
0844:
0845: return list;
0846: }
0847: }
0848:
0849: return org.openide.util.Enumerations.empty();
0850: }
0851:
0852: public GrammarResult queryDefault(HintContext ctx) {
0853: //??? XSLT defaults are missing
0854: if (resultGrammarQuery == null)
0855: return null;
0856: return resultGrammarQuery.queryDefault(ctx);
0857: }
0858:
0859: public boolean isAllowed(Enumeration en) {
0860: return true; //!!! not implemented
0861: }
0862:
0863: public Enumeration queryEntities(String prefix) {
0864: QueueEnumeration list = new QueueEnumeration();
0865:
0866: // add well-know build-in entity names
0867:
0868: if ("lt".startsWith(prefix))
0869: list.put(new MyEntityReference("lt")); // NOI18N
0870: if ("gt".startsWith(prefix))
0871: list.put(new MyEntityReference("gt")); // NOI18N
0872: if ("apos".startsWith(prefix))
0873: list.put(new MyEntityReference("apos")); // NOI18N
0874: if ("quot".startsWith(prefix))
0875: list.put(new MyEntityReference("quot")); // NOI18N
0876: if ("amp".startsWith(prefix))
0877: list.put(new MyEntityReference("amp")); // NOI18N
0878:
0879: return list;
0880: }
0881:
0882: public Enumeration queryNotations(String prefix) {
0883: return org.openide.util.Enumerations.empty();
0884: }
0885:
0886: public java.awt.Component getCustomizer(HintContext ctx) {
0887: if (customizer == null) {
0888: customizer = lookupCustomizerInstance();
0889: if (customizer == null) {
0890: return null;
0891: }
0892: }
0893:
0894: return customizer.getCustomizer(ctx, dataObject);
0895: }
0896:
0897: public boolean hasCustomizer(HintContext ctx) {
0898: if (customizer == null) {
0899: customizer = lookupCustomizerInstance();
0900: if (customizer == null) {
0901: return false;
0902: }
0903: }
0904:
0905: return customizer.hasCustomizer(ctx);
0906: }
0907:
0908: public org.openide.nodes.Node.Property[] getProperties(
0909: final HintContext ctx) {
0910:
0911: if (ctx.getNodeType() != Node.ATTRIBUTE_NODE
0912: || ctx.getNodeValue() == null) {
0913: return null;
0914: }
0915:
0916: PropertySupport attrNameProp = new PropertySupport(
0917: "Attribute name",
0918: String.class, // NOI18N
0919: bundle.getString("BK0001"), bundle.getString("BK0002"),
0920: true, false) {
0921: public void setValue(Object value) {
0922: // Dummy
0923: }
0924:
0925: public Object getValue() {
0926: return ctx.getNodeName();
0927: }
0928:
0929: };
0930:
0931: PropertySupport attrValueProp = new PropertySupport(
0932: "Attribute value",
0933: String.class, // NOI18N
0934: bundle.getString("BK0003"), bundle.getString("BK0004"),
0935: true, true) {
0936: public void setValue(Object value) {
0937: ctx.setNodeValue((String) value);
0938: }
0939:
0940: public Object getValue() {
0941: return ctx.getNodeValue();
0942: }
0943:
0944: };
0945:
0946: return new org.openide.nodes.Node.Property[] { attrNameProp,
0947: attrValueProp };
0948: }
0949:
0950: ////////////////////////////////////////////////////////////////////////////////
0951: // Private helper methods
0952:
0953: /**
0954: * Looks up registered XSLCustomizer objects which will be used by this object
0955: */
0956: private static XSLCustomizer lookupCustomizerInstance() {
0957: Lookup.Template template = new Lookup.Template(
0958: XSLCustomizer.class);
0959:
0960: Lookup.Item lookupItem = Lookups.forPath(CUSTOMIZER_FOLDER)
0961: .lookupItem(template);
0962: if (lookupItem == null) {
0963: return null;
0964: }
0965:
0966: return (XSLCustomizer) lookupItem.getInstance();
0967: }
0968:
0969: /**
0970: * @param enumX the Enumeration which the element should be added to
0971: * @param elements a set containing strings which should be added (with prefix) to the enum or <code>null</null>
0972: * @param namespacePrefix a prefix at the form "xsl:" which should be added in front
0973: * of the names in the elements.
0974: * @param startWith Elements should only be added to enum if they start with this string
0975: */
0976: private static void addXslElementsToEnum(QueueEnumeration enumX,
0977: Set elements, String namespacePrefix, String startWith) {
0978: if (elements == null)
0979: return;
0980: if (startWith.startsWith(namespacePrefix)
0981: || namespacePrefix.startsWith(startWith)) {
0982: Iterator it = elements.iterator();
0983: while (it.hasNext()) {
0984: Object next = it.next();
0985: if (next != resultElements) {
0986: String nextText = namespacePrefix + (String) next;
0987: if (nextText.startsWith(startWith)) {
0988: // TODO pass true for empty elements
0989: enumX.put(new MyElement(nextText, false));
0990: }
0991: }
0992: }
0993: }
0994: }
0995:
0996: private static void addItemsToEnum(QueueEnumeration enumX, Set set,
0997: String startWith, String prefix) {
0998: Iterator it = set.iterator();
0999: while (it.hasNext()) {
1000: String nextText = (String) it.next();
1001: if (nextText.startsWith(startWith)) {
1002: enumX.put(new MyText(prefix + nextText));
1003: }
1004: }
1005: }
1006:
1007: /**
1008: * This method traverses up the document tree, investigates it and updates
1009: * prefixList, resultGrammarQuery, lastDoctypeSystem or lastDoctypePublic
1010: * members if necessery.
1011: * @param curNode the node which from wich the traversing should start.
1012: */
1013: private void updateProperties(Node curNode) {
1014: prefixList.clear();
1015:
1016: // Traverse up the documents tree
1017: Node rootNode = curNode;
1018: while (curNode != null && !(curNode instanceof Document)) {
1019:
1020: // Update the xsl namespace prefix list
1021: NamedNodeMap attributes = curNode.getAttributes();
1022: for (int ind = 0; ind < attributes.getLength(); ind++) {
1023: Attr attr = (Attr) attributes.item(ind);
1024: String attrName = attr.getName();
1025: if (attrName != null && attrName.startsWith("xmlns:")) { // NOI18N
1026: if (attr.getValue().equals(XSLT_NAMESPACE_URI)) {
1027: prefixList.add(0, attrName.substring(6));
1028: }
1029: }
1030: }
1031:
1032: rootNode = curNode;
1033: curNode = rootNode.getParentNode();
1034: }
1035:
1036: boolean outputFound = false;
1037: if (prefixList.size() > 0) {
1038: String outputElName = (String) prefixList.get(0)
1039: + ":output"; // NOI18N
1040: Node childOfRoot = rootNode.getFirstChild();
1041: while (childOfRoot != null) {
1042: String childNodeName = childOfRoot.getNodeName();
1043: if (childNodeName != null
1044: && childNodeName.equals(outputElName)) {
1045: Element outputEl = (Element) childOfRoot;
1046: String outputMethod = outputEl
1047: .getAttribute("method"); // NOI18N
1048:
1049: String curDoctypePublic = outputEl
1050: .getAttribute("doctype-public"); // NOI18N
1051: String curDoctypeSystem = outputEl
1052: .getAttribute("doctype-system"); // NOI18N
1053:
1054: if ("html".equals(outputMethod) // NOI18N
1055: && (curDoctypePublic == null || curDoctypePublic
1056: .length() == 0)
1057: && (curDoctypeSystem == null || curDoctypeSystem
1058: .length() == 0)) { // NOI18N
1059: // html is special case that can be emulated using XHTML
1060: curDoctypePublic = XHTML_PUBLIC_ID;
1061: curDoctypeSystem = XHTML_SYSTEM_ID;
1062: } else if ("text".equals(outputMethod)) { // NOI18N
1063: // user error, ignore
1064: break;
1065: }
1066:
1067: if (curDoctypePublic != null
1068: && !curDoctypePublic
1069: .equals(lastDoctypePublic)
1070: || curDoctypePublic == null
1071: && lastDoctypePublic != null
1072: || curDoctypeSystem != null
1073: && !curDoctypeSystem
1074: .equals(lastDoctypeSystem)
1075: || curDoctypeSystem == null
1076: && lastDoctypeSystem != null) {
1077: setOutputDoctype(curDoctypePublic,
1078: curDoctypeSystem);
1079: }
1080:
1081: outputFound = true;
1082: break;
1083: }
1084: childOfRoot = childOfRoot.getNextSibling();
1085: }
1086: }
1087:
1088: if (!outputFound) {
1089: setOutputDoctype(null, null);
1090: }
1091: }
1092:
1093: /**
1094: * Updates resultGrammarQuery by parsing the DTD specified by publicId and
1095: * systemId. lastDoctypeSystem and lastDoctypePublic are assigned to the new values.
1096: * @param publicId the public identifier of the DTD
1097: * @param publicId the system identifier of the DTD
1098: */
1099: private void setOutputDoctype(String publicId, String systemId) {
1100: lastDoctypePublic = publicId;
1101: lastDoctypeSystem = systemId;
1102:
1103: if (publicId == null && systemId == null) {
1104: resultGrammarQuery = null;
1105: return;
1106: }
1107:
1108: InputSource inputSource = null;
1109: UserCatalog catalog = UserCatalog.getDefault();
1110: if (catalog != null) {
1111: EntityResolver resolver = catalog.getEntityResolver();
1112: if (resolver != null) {
1113: try {
1114: inputSource = resolver.resolveEntity(publicId,
1115: systemId);
1116: } catch (SAXException e) {
1117: } catch (IOException e) {
1118: } // Will be handled below
1119: }
1120: }
1121:
1122: if (inputSource == null) {
1123: try {
1124: java.net.URL url = new java.net.URL(systemId);
1125: inputSource = new InputSource(url.openStream());
1126: inputSource.setPublicId(publicId);
1127: inputSource.setSystemId(systemId);
1128: } catch (IOException e) {
1129: resultGrammarQuery = null;
1130: return;
1131: }
1132: }
1133:
1134: resultGrammarQuery = DTDUtil.parseDTD(true, inputSource);
1135:
1136: }
1137:
1138: ////////////////////////////////////////////////////////////////////////////////
1139: // Private helper classes
1140:
1141: private class ResultHintContext extends ResultNode implements
1142: HintContext {
1143: private String currentPrefix;
1144:
1145: public ResultHintContext(HintContext peer, String ignorePrefix,
1146: String onlyUsePrefix) {
1147: super (peer, ignorePrefix, onlyUsePrefix);
1148: currentPrefix = peer.getCurrentPrefix();
1149: }
1150:
1151: public String getCurrentPrefix() {
1152: return currentPrefix;
1153: }
1154: }
1155:
1156: // Result classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1157:
1158: private static abstract class AbstractResultNode extends
1159: AbstractNode implements GrammarResult {
1160:
1161: public Icon getIcon(int kind) {
1162: return null;
1163: }
1164:
1165: /**
1166: * @return provide additional information simplifiing decision
1167: */
1168: public String getDescription() {
1169: return NbBundle.getMessage(XSLGrammarQuery.class, "BK0005");
1170: }
1171:
1172: /**
1173: * @return text representing name of suitable entity
1174: * //??? is it really needed
1175: */
1176: public String getText() {
1177: return getNodeName();
1178: }
1179:
1180: /**
1181: * @return name that is presented to user
1182: */
1183: public String getDisplayName() {
1184: return null;
1185: }
1186:
1187: public boolean isEmptyElement() {
1188: return false;
1189: }
1190:
1191: }
1192:
1193: private static class MyEntityReference extends AbstractResultNode
1194: implements EntityReference {
1195:
1196: private String name;
1197:
1198: MyEntityReference(String name) {
1199: this .name = name;
1200: }
1201:
1202: public short getNodeType() {
1203: return Node.ENTITY_REFERENCE_NODE;
1204: }
1205:
1206: public String getNodeName() {
1207: return name;
1208: }
1209:
1210: }
1211:
1212: private static class MyElement extends AbstractResultNode implements
1213: Element {
1214:
1215: private String name;
1216: private boolean empty;
1217:
1218: MyElement(String name, boolean empty) {
1219: this .name = name;
1220: this .empty = empty;
1221: }
1222:
1223: public short getNodeType() {
1224: return Node.ELEMENT_NODE;
1225: }
1226:
1227: public String getNodeName() {
1228: return name;
1229: }
1230:
1231: public String getTagName() {
1232: return name;
1233: }
1234:
1235: public boolean isEmptyElement() {
1236: return empty;
1237: }
1238:
1239: }
1240:
1241: private static class MyAttr extends AbstractResultNode implements
1242: Attr {
1243:
1244: private String name;
1245:
1246: MyAttr(String name) {
1247: this .name = name;
1248: }
1249:
1250: public short getNodeType() {
1251: return Node.ATTRIBUTE_NODE;
1252: }
1253:
1254: public String getNodeName() {
1255: return name;
1256: }
1257:
1258: public String getName() {
1259: return name;
1260: }
1261:
1262: public String getValue() {
1263: return null; //??? what spec says
1264: }
1265:
1266: }
1267:
1268: private static class MyText extends AbstractResultNode implements
1269: Text {
1270:
1271: private String data;
1272:
1273: MyText(String data) {
1274: this .data = data;
1275: }
1276:
1277: public short getNodeType() {
1278: return Node.TEXT_NODE;
1279: }
1280:
1281: public String getNodeValue() {
1282: return getData();
1283: }
1284:
1285: public String getData() throws DOMException {
1286: return data;
1287: }
1288:
1289: public int getLength() {
1290: return data == null ? -1 : data.length();
1291: }
1292: }
1293:
1294: private static class QueueEnumeration implements Enumeration {
1295: private java.util.LinkedList list = new LinkedList();
1296:
1297: public boolean hasMoreElements() {
1298: return !list.isEmpty();
1299: }
1300:
1301: public Object nextElement() {
1302: return list.removeFirst();
1303: }
1304:
1305: public void put(Object[] arr) {
1306: list.addAll(Arrays.asList(arr));
1307: }
1308:
1309: public void put(Object o) {
1310: list.add(o);
1311: }
1312:
1313: } // end of QueueEnumeration
1314: }
|