2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.xml.tools.generator;
44
import java.io.IOException;
45
import java.io.OutputStream;
46
import java.io.OutputStreamWriter;
47
import java.io.PrintWriter;
48
import java.io.UnsupportedEncodingException;
49
import java.io.Writer;
51
import java.util.Collection;
52
import java.util.HashSet;
53
import java.util.Iterator;
54
import java.util.LinkedHashMap;
57
import java.util.Stack;
58
import java.util.Vector;
59
import org.netbeans.api.xml.services.UserCatalog;
60
import org.netbeans.modules.xml.core.XMLDataObject;
61
import org.netbeans.modules.xml.core.lib.GuiUtil;
62
import org.netbeans.tax.TreeUtilities;
63
import org.openide.cookies.SaveCookie;
64
import org.openide.filesystems.FileLock;
65
import org.openide.filesystems.FileObject;
66
import org.openide.filesystems.FileStateInvalidException;
67
import org.openide.loaders.DataObject;
68
import org.openide.util.UserCancelException;
69
import org.openide.xml.XMLUtil;
70
import org.xml.sax.Attributes;
71
import org.xml.sax.ContentHandler;
72
import org.xml.sax.EntityResolver;
73
import org.xml.sax.ErrorHandler;
74
import org.xml.sax.InputSource;
75
import org.xml.sax.Locator;
76
import org.xml.sax.SAXException;
77
import org.xml.sax.SAXParseException;
78
import org.xml.sax.XMLReader;
81
* GenerateDTDSupport class generate a DTD by guessing it from
84
* It's already prepared for plugging in XMLSchema generator.
86
* @author Libor Kramolis
87
* @author Petr Kuzel, rewritten to SAX
89
public final class GenerateDTDSupport implements XMLGenerateCookie {
90
static final String DTD_EXT = "dtd"; // NOI18N
93
* XML document data object actiang as a "template".
95
private final DataObject template;
96
private ElementInfo current;
97
private Stack elementStack;
99
private Map elementInfos;
100
private String warning;
101
private String rootQName;
105
* @param template data object that is a "template" for created DTD
107
public GenerateDTDSupport(XMLDataObject template) {
108
this.template = template;
115
* Performs a dialog with a user and generates the DTD
117
public void generate() {
120
// saving file before DTD generation
121
SaveCookie save = (SaveCookie)template.getCookie(SaveCookie.class);
122
if (save!=null) save.save();
124
FileObject primFile = template.getPrimaryFile();
125
String name = primFile.getName();
126
FileObject folder = primFile.getParent();
128
FileObject generFile = (new SelectFileDialog(folder, name, DTD_EXT, Util.NONEMPTY_CHECK)).getFileObject();
129
name = generFile.getName();
131
// IANA encoding name
132
String encoding = "UTF-8";
133
String dtd = xml2dtd(name, encoding);
135
String msg = Util.THIS.getString("BK0009");
136
GuiUtil.notifyWarning(msg + "\n" + warning); // NOI18N
141
FileLock lock = null;
142
Writer writer = null;
144
lock = generFile.lock();
145
encoding = TreeUtilities.iana2java(encoding == null ? "UTF-8" : encoding); // NOI18N
146
OutputStream output = generFile.getOutputStream(lock);
148
writer = new OutputStreamWriter(output, encoding);
149
} catch (UnsupportedEncodingException e) {
150
writer = new OutputStreamWriter(output);
152
writer = new PrintWriter(writer);
153
writer.write(dtd.toString());
162
// disabled until in-memory XML model is not performance problem
163
// trySetDocumentType(name);
165
GuiUtil.performDefaultAction(generFile);
167
} catch (UserCancelException e) {
168
// } catch (FileStateInvalidException e) {
169
// } catch (TreeException e) {
170
// } catch (IOException e) {
171
} catch (Exception exc) {
172
GuiUtil.notifyException(exc);
178
// * Update template's DOCTYPE to point to generated DTD.
180
// private void trySetDocumentType(String fileName) {
181
// if (templateRoot.getParentNode() instanceof TreeDocument) { // try to set only when element is root document element
182
// TreeDocument document = (TreeDocument) templateRoot.getParentNode();
183
// if (GuiUtil.confirmAction(Util.THIS.getString("MSG_use_dtd_as_document_type?"))) {
185
// TreeDocumentType newDoctype = new TreeDocumentType(templateRoot.getQName(), null, fileName + "." + DTD_EXT); // NOI18N
186
// document.setDocumentType(newDoctype);
187
// } catch (TreeException exc) {
188
// GuiUtil.notifyWarning(exc.getLocalizedMessage());
196
* Generate the DTD into temporary string.
197
* @return null if problems leaved in <code>warning</code> field occured
200
String xml2dtd(String name, String encoding) {
201
StringBuffer sb = new StringBuffer();
202
elementStack = new Stack();
203
elementInfos = new LinkedHashMap(101);
205
// fill table of dtd declarations
206
if (false == scanTemplate()) {
210
if (encoding != null) {
211
sb.append("<?xml version='1.0' encoding='").append(encoding).append("'?>\n\n"); // NOI18N
214
String todo = Util.THIS.getString("TODO", name + "." + DTD_EXT);
215
sb.append("<!--\n ").append(todo).append("\n\n-->");
217
String usage = Util.THIS.getString("BK0010");
218
sb.append("<!--\n").append(" " + usage + "\n\n").append(" <?xml version=\"1.0\"?>\n\n").// NOI18N
219
append(" <!DOCTYPE ").append(rootQName).append(" SYSTEM \"").// NOI18N
220
append(name).append(".").append(DTD_EXT).append("\">\n\n").// NOI18N
221
append(" <").append(rootQName).append(">\n ...\n").append(" </").append(rootQName).append(">\n").// NOI18N
222
append("-->\n"); // NOI18N
224
// generate DTD contaent by the table
225
//??? we could easily plug here XML Schema generator
227
Iterator it = elementInfos.values().iterator();
229
while (it.hasNext()) {
230
sb.append("\n"); // NOI18N
231
elem = (ElementInfo) it.next();
233
sb.append("<!--- " + Util.THIS.getString("FMT_DTDDoc") + " -->\n");
234
//!!! there may by clash if the doccument happens to map several
235
// URI into one prefix
236
sb.append("<!ELEMENT ").append(elem.name.qName).append(" "); // NOI18N
239
sb.append("EMPTY"); // NOI18N
241
Collection collect = elem.children;
242
if ((elem.pcdata == true) ||
243
(collect.size() == 0)) {
244
Vector vect = new Vector(collect);
245
vect.insertElementAt(new XName("","","#PCDATA"), 0); // NOI18N
248
Iterator itc = collect.iterator();
250
elemName = (XName) itc.next();
251
sb.append("(").append(elemName.qName); // NOI18N
252
while (itc.hasNext()) {
253
elemName = (XName) itc.next();
254
sb.append("|").append(elemName.qName); // NOI18N
258
if (false == sb.toString().endsWith("#PCDATA")) { // NOI18N
259
sb.append(")*"); // NOI18N
261
sb.append(")"); // NOI18N
264
sb.append(">\n"); // NOI18N
267
if (elem.attributes.size() != 0) {
268
sb.append("<!ATTLIST ").append(elem.name.qName).append("\n"); // NOI18N
269
Iterator ita = elem.attributes.iterator();
270
while (ita.hasNext()) {
271
XName attName = (XName) ita.next();
272
sb.append(" ").append(attName.qName).append(" CDATA #IMPLIED\n"); // NOI18N
274
sb.append(" >\n"); // NOI18N
278
return sb.toString();
282
* Using SAX events fill elementsInfo map.
283
* @return false in parsing errors have occured.
285
private boolean scanTemplate() {
287
XMLReader parser = null;
289
url = template.getPrimaryFile().getURL();
290
} catch (FileStateInvalidException e) {
291
warning = e.getLocalizedMessage();
295
String system = url.toExternalForm();
297
parser = XMLUtil.createXMLReader(false, true);
298
Impl impl = new Impl();
299
parser.setContentHandler(impl);
300
parser.setErrorHandler(impl);
301
parser.setFeature("http://xml.org/sax/features/namespace-prefixes", true); // NOI18N
302
UserCatalog catalog = UserCatalog.getDefault();
303
if (catalog != null) {
304
EntityResolver resolver = catalog.getEntityResolver();
305
if (resolver != null) {
306
parser.setEntityResolver(resolver);
309
} catch (SAXException e) {
310
warning = e.getLocalizedMessage();
314
InputSource input = new InputSource(system);
318
} catch (IOException e) {
319
warning = e.getLocalizedMessage();
321
} catch (SAXException e) {
322
warning = e.getLocalizedMessage();
330
* Return true if parameter contains just white spaces.
332
private boolean wsOnly(String s) {
333
if (s == null) return true;
335
char[] data = s.toCharArray();
336
for (int i = 0; i < data.length; i++) {
337
if (Character.isWhitespace(data[i]) == false) {
346
* Return true if parameter contains just white spaces.
348
private boolean wsOnly(char[] data, int from, int length) {
349
for (int i = from; i < from + length; i++) {
350
if (Character.isWhitespace(data[i]) == false) {
358
// SAX2 Content handler methods
360
private class Impl implements ContentHandler, ErrorHandler {
361
public void characters(char[] chars, int i, int i1) throws SAXException {
362
if (false == wsOnly(chars, i, i1)) {
363
if (current != null) {
369
public void endDocument() throws SAXException {
372
public void endElement(String s, String s1, String s2) throws SAXException {
373
current = (ElementInfo) elementStack.pop();
376
public void endPrefixMapping(String s) throws SAXException {
379
public void ignorableWhitespace(char[] chars, int i, int i1) throws SAXException {
382
public void processingInstruction(String s, String s1) throws SAXException {
385
public void setDocumentLocator(Locator locator) {
388
public void skippedEntity(String s) throws SAXException {
391
public void startDocument() throws SAXException {
394
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
396
if (rootQName == null) {
400
XName xName = new XName(uri, localName, qName);
401
ElementInfo info = (ElementInfo) elementInfos.get(xName);
403
info = new ElementInfo(uri, localName, qName);
404
elementInfos.put(xName, info);
406
for (int i = 0; i<attributes.getLength(); i++) {
407
// what is URI of the "xmlns:" prefix?
408
info.addAttribute(attributes.getURI(i), attributes.getLocalName(i), attributes.getQName(i));
411
if (current != null) {
412
current.addChild(info);
414
elementStack.push(current);
418
public void startPrefixMapping(String s, String s1) throws SAXException {
421
public void error(SAXParseException e) throws SAXException {
425
public void fatalError(SAXParseException e) throws SAXException {
429
public void warning(SAXParseException e) throws SAXException {
435
* Holds all info gathered while scanning template document.
437
private class ElementInfo {
444
public ElementInfo(String uri, String localName, String qName) {
445
name = new XName(uri, localName, qName);
446
children = new HashSet();
447
attributes = new HashSet();
452
public void hasPCDATA() {
457
public boolean isTextAllowed() {
461
public void addChild(ElementInfo info) {
463
children.add(info.name);
466
public void addAttribute(String uri, String localName, String qName) {
467
attributes.add(new XName(uri, localName, qName));
470
public boolean equals(Object obj) {
471
if (this == obj) return true;
472
if (obj instanceof ElementInfo) {
473
ElementInfo info = (ElementInfo) obj;
474
return name.equals(info.name);
479
public int hashCode() {
480
return name.hashCode();
482
} // end of inner class ElementInfo
485
* Structured XML name with value based identity.
486
* The identity onors all URI, localName, qName. It's usefull
487
* for DTD generators. XML Schema generators must remove
488
* {URI}localName duplicities caused by qNames.
490
private static class XName {
491
private String uri, localName, qName;
492
public XName(String uri, String localName, String qName) {
494
this.localName = localName;
498
public boolean equals(Object peer) {
499
if (peer == this) return true;
500
if (peer instanceof XName) {
501
XName id = (XName) peer;
502
return uri.equals(id.uri)
503
&& localName.equals(id.localName)
504
&& qName.equals(id.qName);
509
public int hashCode() {
510
return uri.hashCode() ^ localName.hashCode() ^ qName.hashCode();