~ubuntu-branches/ubuntu/precise/xom/precise

« back to all changes in this revision

Viewing changes to src/nu/xom/XOMHandler.java

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2007-11-25 15:50:40 UTC
  • Revision ID: james.westby@ubuntu.com-20071125155040-r75ikcqf1vu0cei7
Tags: upstream-1.1
ImportĀ upstreamĀ versionĀ 1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright 2002-2005 Elliotte Rusty Harold
 
2
   
 
3
   This library is free software; you can redistribute it and/or modify
 
4
   it under the terms of version 2.1 of the GNU Lesser General Public 
 
5
   License as published by the Free Software Foundation.
 
6
   
 
7
   This library is distributed in the hope that it will be useful,
 
8
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 
10
   GNU Lesser General Public License for more details.
 
11
   
 
12
   You should have received a copy of the GNU Lesser General Public
 
13
   License along with this library; if not, write to the 
 
14
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 
15
   Boston, MA 02111-1307  USA
 
16
   
 
17
   You can contact Elliotte Rusty Harold by sending e-mail to
 
18
   elharo@metalab.unc.edu. Please include the word "XOM" in the
 
19
   subject line. The XOM home page is located at http://www.xom.nu/
 
20
*/
 
21
 
 
22
 
 
23
package nu.xom;
 
24
 
 
25
import java.util.ArrayList;
 
26
 
 
27
import org.xml.sax.ContentHandler;
 
28
import org.xml.sax.DTDHandler;
 
29
import org.xml.sax.Locator;
 
30
import org.xml.sax.ext.DeclHandler;
 
31
import org.xml.sax.ext.LexicalHandler;
 
32
 
 
33
/**
 
34
 * @author Elliotte Rusty Harold
 
35
 * @version 1.1b7
 
36
 *
 
37
 */
 
38
class XOMHandler 
 
39
  implements ContentHandler, LexicalHandler, DeclHandler, DTDHandler {
 
40
 
 
41
    protected Document     document;
 
42
    protected String       documentBaseURI;
 
43
    
 
44
    // parent is never null. It is the node we're adding children 
 
45
    // to. current corresponds to the most recent startElement()
 
46
    // method and may be null if we've skipped it (makeElement
 
47
    // returned null.) If we didn't skip it, then parent and
 
48
    // current should be the same node.
 
49
    protected ParentNode   parent;
 
50
    protected ParentNode   current;
 
51
    protected ArrayList    parents;
 
52
    protected boolean      inProlog;
 
53
    protected boolean      inDTD;
 
54
    protected int          position; // current number of items in prolog
 
55
    protected Locator      locator; 
 
56
    protected DocType      doctype;
 
57
    protected StringBuffer internalDTDSubset;
 
58
    protected NodeFactory  factory;
 
59
              boolean      usingCrimson = false;
 
60
    
 
61
    
 
62
    XOMHandler(NodeFactory factory) {
 
63
        this.factory = factory; 
 
64
    }   
 
65
    
 
66
    
 
67
    public void setDocumentLocator(Locator locator) {
 
68
        this.locator = locator;
 
69
    }
 
70
 
 
71
    
 
72
    Document getDocument() {
 
73
        return document;
 
74
    }
 
75
 
 
76
 
 
77
    // See http://www.servlets.com/archive/servlet/ReadMsg?msgId=554071&listName=jdom-interest
 
78
    // This method is called to avoid leaking document sized leaking memory
 
79
    // when a Builder is not imediately reused
 
80
    void freeMemory() {
 
81
        document = null;
 
82
        parent = null;
 
83
        current = null;
 
84
        parents = null;
 
85
        locator = null;
 
86
        doctype = null;
 
87
        internalDTDSubset = null;
 
88
    }
 
89
 
 
90
    
 
91
    public void startDocument() {
 
92
        
 
93
        inDTD = false;
 
94
        document = factory.startMakingDocument();
 
95
        parent = document;
 
96
        current = document;
 
97
        parents = new ArrayList();
 
98
        parents.add(document);
 
99
        inProlog = true;
 
100
        position = 0;
 
101
        textString = null;
 
102
        doctype = null;
 
103
        if (locator != null) {
 
104
            documentBaseURI = locator.getSystemId();
 
105
            // According to the XML spec, 
 
106
            // "It is an error for a fragment identifier 
 
107
            // (beginning with a # character) to be part of a system identifier"
 
108
            // but some parsers including Xerces seem to get this wrong, so we'll 
 
109
            document.setBaseURI(documentBaseURI);
 
110
        }
 
111
        buffer = null;
 
112
        
 
113
    }
 
114
  
 
115
    
 
116
    public void endDocument() {
 
117
        factory.finishMakingDocument(document);
 
118
        parents.remove(parents.size()-1);
 
119
    }
 
120
  
 
121
    
 
122
    public void startElement(String namespaceURI, String localName, 
 
123
      String qualifiedName, org.xml.sax.Attributes attributes) {
 
124
        
 
125
        flushText();
 
126
        Element element;
 
127
        if (parent != document) {
 
128
            element = factory.startMakingElement(qualifiedName, namespaceURI);
 
129
        }
 
130
        else {  // root
 
131
            element = factory.makeRootElement(qualifiedName, namespaceURI);
 
132
            if (element == null) { // null root; that's a no-no
 
133
                throw new NullPointerException(
 
134
                    "Factory failed to create root element."
 
135
                );   
 
136
            }
 
137
            document.setRootElement(element);
 
138
            inProlog = false;
 
139
        }
 
140
        
 
141
        current = element;
 
142
        // Need to push this, even if it's null 
 
143
        parents.add(element);
 
144
        
 
145
        if (element != null) { // wasn't filtered out
 
146
            if (parent != document) { 
 
147
                // a.k.a. parent not instanceof Document
 
148
                parent.appendChild(element);
 
149
            }
 
150
            // This is optimized for the very common case where 
 
151
            // everything in the document has the same actual base URI. 
 
152
            // It may add redundant base URIs in cases like XInclude 
 
153
            // where different parts of the document have different 
 
154
            // base URIs.
 
155
            if (locator != null) {
 
156
                 String baseURI = locator.getSystemId();
 
157
                 if (baseURI != null && !baseURI.equals(documentBaseURI)) {
 
158
                     element.setActualBaseURI(baseURI);
 
159
                 }
 
160
            }         
 
161
            
 
162
            // Attach the attributes; this must be done before the
 
163
            // namespaces are attached.      
 
164
            // XXX pull out length
 
165
            
 
166
            // XXX we've got a pretty good guess at how many attributes there
 
167
            // will be here; we should ensureCapacity up to that length
 
168
            for (int i = 0; i < attributes.getLength(); i++) {
 
169
                String qName = attributes.getQName(i);
 
170
                if (qName.startsWith("xmlns:") || qName.equals("xmlns")) {               
 
171
                    continue;               
 
172
                }             
 
173
                else {
 
174
                    String namespace = attributes.getURI(i);
 
175
                    String value = attributes.getValue(i);
 
176
                    Nodes nodes = factory.makeAttribute(
 
177
                      qName, 
 
178
                      namespace, 
 
179
                      value, 
 
180
                      convertStringToType(attributes.getType(i))
 
181
                    );
 
182
                    int numberChildren = 0;
 
183
                    for (int j=0; j < nodes.size(); j++) {
 
184
                        Node node = nodes.get(j);
 
185
                        if (node.isAttribute()) {
 
186
                            factory.addAttribute(element, (Attribute) node);
 
187
                        }
 
188
                        else {
 
189
                            factory.insertChild(element, node, numberChildren++);   
 
190
                        }
 
191
                    }
 
192
                }
 
193
            }
 
194
 
 
195
            // Attach the namespaces
 
196
            for (int i = 0; i < attributes.getLength(); i++) {
 
197
                String qName = attributes.getQName(i);
 
198
                if (qName.startsWith("xmlns:")) {               
 
199
                    String namespaceName = attributes.getValue(i);
 
200
                    String namespacePrefix = qName.substring(6);
 
201
                    String currentValue
 
202
                       = element.getNamespaceURI(namespacePrefix); 
 
203
                    if (!namespaceName.equals(currentValue) && ! namespacePrefix.equals(element.getNamespacePrefix())) {
 
204
                        element.addNamespaceDeclaration(
 
205
                          namespacePrefix, namespaceName);
 
206
                    }              
 
207
                }   
 
208
                else if (qName.equals("xmlns")) {               
 
209
                    String namespaceName = attributes.getValue(i);
 
210
                    String namespacePrefix = "";
 
211
                    String currentValue 
 
212
                      = element.getNamespaceURI(namespacePrefix); 
 
213
                    if (!namespaceName.equals(currentValue) && ! "".equals(element.getNamespacePrefix())) {
 
214
                        element.addNamespaceDeclaration(namespacePrefix, 
 
215
                         namespaceName);
 
216
                    }                
 
217
                }             
 
218
            }
 
219
            
 
220
            // this is the new parent
 
221
            parent = element;
 
222
        }
 
223
        
 
224
    }
 
225
 
 
226
    
 
227
    public void endElement(
 
228
      String namespaceURI, String localName, String qualifiedName) {
 
229
        
 
230
        // If we're immediately inside a skipped element
 
231
        // we need to reset current to null, not to the parent
 
232
        current = (ParentNode) parents.remove(parents.size()-1);
 
233
        flushText();
 
234
        
 
235
        if (current != null) {
 
236
            parent = current.getParent();
 
237
            Nodes result = factory.finishMakingElement((Element) current);
 
238
            
 
239
            // Optimization for default case where result only contains current
 
240
            if (result.size() != 1 || result.get(0) != current) {            
 
241
                if (!parent.isDocument()) {
 
242
                    // allow factories to detach the element itself in
 
243
                    // finishMakingElement
 
244
                    int childCount = parent.getChildCount();
 
245
                    try {
 
246
                        parent.removeChild(childCount - 1);
 
247
                    }
 
248
                    catch (IndexOutOfBoundsException ex) {
 
249
                        throw new XMLException(
 
250
                          "Factory detached element in finishMakingElement()", 
 
251
                          ex);
 
252
                    }
 
253
                    for (int i=0; i < result.size(); i++) {
 
254
                        Node node = result.get(i);
 
255
                         if (node.isAttribute()) {
 
256
                             ((Element) parent).addAttribute((Attribute) node);
 
257
                         }
 
258
                         else {
 
259
                             parent.appendChild(node);   
 
260
                         }
 
261
                    }
 
262
                }
 
263
                else { // root element
 
264
                    Document doc = (Document) parent;
 
265
                    Element currentRoot = doc.getRootElement();
 
266
                    boolean beforeRoot = true;
 
267
                    for (int i=0; i < result.size(); i++) {
 
268
                        Node node = result.get(i);
 
269
                        if (node.isElement()) {
 
270
                            if (node != currentRoot) {   
 
271
                                if (!beforeRoot) {
 
272
                                    // already set root, oops
 
273
                                    throw new IllegalAddException("Factory returned multiple roots");   
 
274
                                }
 
275
                                doc.setRootElement((Element) node);
 
276
                            }
 
277
                            beforeRoot = false;
 
278
                        }
 
279
                        else if (beforeRoot) {
 
280
                            doc.insertChild(node, doc.indexOf(doc.getRootElement()));   
 
281
                        }
 
282
                        else {
 
283
                            doc.appendChild(node);   
 
284
                        }
 
285
                    }
 
286
                    if (beforeRoot) {
 
287
                        // somebody tried to replace the root element with
 
288
                        // no element at all. That's a no-no
 
289
                        throw new WellformednessException(
 
290
                          "Factory attempted to remove the root element");
 
291
                    }
 
292
                }
 
293
            }
 
294
        }
 
295
        
 
296
    }
 
297
    
 
298
    
 
299
    static Attribute.Type convertStringToType(String saxType) {
 
300
    
 
301
        if (saxType.equals("CDATA"))    return Attribute.Type.CDATA;
 
302
        if (saxType.equals("ID"))       return Attribute.Type.ID;
 
303
        if (saxType.equals("IDREF"))    return Attribute.Type.IDREF;
 
304
        if (saxType.equals("IDREFS"))   return Attribute.Type.IDREFS;
 
305
        if (saxType.equals("NMTOKEN"))  return Attribute.Type.NMTOKEN;
 
306
        if (saxType.equals("NMTOKENS")) return Attribute.Type.NMTOKENS;
 
307
        if (saxType.equals("ENTITY"))   return Attribute.Type.ENTITY;
 
308
        if (saxType.equals("ENTITIES")) return Attribute.Type.ENTITIES;
 
309
        if (saxType.equals("NOTATION")) return Attribute.Type.NOTATION;
 
310
        
 
311
        // non-standard but some parsers use this
 
312
        if (saxType.equals("ENUMERATION")) {
 
313
            return Attribute.Type.ENUMERATION;
 
314
        } 
 
315
        if (saxType.startsWith("(")) return Attribute.Type.ENUMERATION;
 
316
    
 
317
        return Attribute.Type.UNDECLARED;
 
318
        
 
319
    }
 
320
  
 
321
    
 
322
    protected String textString = null;
 
323
    protected StringBuffer buffer = null;
 
324
  
 
325
    public void characters(char[] text, int start, int length) {
 
326
        
 
327
        if (length <= 0) return;
 
328
        if (textString == null) textString = new String(text, start, length);
 
329
        else {
 
330
            if (buffer == null) buffer = new StringBuffer(textString);
 
331
            buffer.append(text, start, length);
 
332
        }
 
333
        if (finishedCDATA) inCDATA = false;
 
334
        
 
335
    }
 
336
 
 
337
    
 
338
    // accumulate all text that's in the buffer into a text node
 
339
    protected void flushText() {
 
340
        
 
341
        if (buffer != null) {
 
342
            textString = buffer.toString();
 
343
            buffer = null;
 
344
        }
 
345
        
 
346
        if (textString != null) {
 
347
            Nodes result;
 
348
            if (!inCDATA) {
 
349
                result = factory.makeText(textString);
 
350
            }
 
351
            else {
 
352
                result = factory.makeCDATASection(textString);
 
353
            }
 
354
            for (int i=0; i < result.size(); i++) {
 
355
                Node node = result.get(i);
 
356
                if (node.isAttribute()) {
 
357
                    ((Element) parent).addAttribute((Attribute) node);
 
358
                }
 
359
                else {
 
360
                    parent.appendChild(node);   
 
361
                }
 
362
            }
 
363
            textString = null;
 
364
        }
 
365
        inCDATA = false;
 
366
        finishedCDATA = false;
 
367
        
 
368
    }
 
369
  
 
370
    
 
371
    public void ignorableWhitespace(
 
372
      char[] text, int start, int length) {
 
373
        characters(text, start, length);
 
374
    }
 
375
  
 
376
    
 
377
    public void processingInstruction(String target, String data) {
 
378
        
 
379
        if (!inDTD) flushText();
 
380
        if (inDTD && !inInternalSubset()) return;
 
381
        Nodes result = factory.makeProcessingInstruction(target, data);
 
382
        
 
383
        for (int i = 0; i < result.size(); i++) {
 
384
            Node node = result.get(i);
 
385
            if (!inDTD) {
 
386
                if (inProlog) {
 
387
                    parent.insertChild(node, position);
 
388
                    position++;
 
389
                }
 
390
                else {
 
391
                    if (node.isAttribute()) {
 
392
                        ((Element) parent).addAttribute((Attribute) node);
 
393
                    }
 
394
                    else parent.appendChild(node);
 
395
                }
 
396
            }
 
397
            else {
 
398
                if (node.isProcessingInstruction() || node.isComment()) {
 
399
                    internalDTDSubset.append("  ");            
 
400
                    internalDTDSubset.append(node.toXML());            
 
401
                    internalDTDSubset.append("\n");            
 
402
                }
 
403
                else {
 
404
                    throw new XMLException("Factory tried to put a " 
 
405
                      + node.getClass().getName() 
 
406
                      + " in the internal DTD subset");   
 
407
                }
 
408
            }            
 
409
        }
 
410
 
 
411
    }
 
412
 
 
413
 
 
414
    // XOM handles this with attribute values; not prefix mappings
 
415
    public void startPrefixMapping(String prefix, String uri) {}
 
416
    public void endPrefixMapping(String prefix) {}
 
417
 
 
418
    public void skippedEntity(String name) {
 
419
        
 
420
        // Xerces 2.7 now calls this method in the DTD 
 
421
        // for parameter entities it doesn't resolve. We can ignore these.
 
422
        if (name.startsWith("%")) return;
 
423
        flushText();
 
424
        throw new XMLException("Could not resolve entity " + name);
 
425
        
 
426
    }
 
427
    
 
428
    
 
429
    // LexicalHandler events
 
430
    public void startDTD(String rootName, String publicID, 
 
431
      String systemID) {
 
432
        
 
433
        inDTD = true;
 
434
        Nodes result = factory.makeDocType(rootName, publicID, systemID);
 
435
        for (int i = 0; i < result.size(); i++) {
 
436
            Node node = result.get(i);
 
437
            document.insertChild(node, position);
 
438
            position++;
 
439
            if (node.isDocType()) {
 
440
                DocType doctype = (DocType) node;
 
441
                internalDTDSubset = new StringBuffer(); 
 
442
                this.doctype = doctype;
 
443
            }
 
444
        }
 
445
        
 
446
    }
 
447
     
 
448
    
 
449
    public void endDTD() {
 
450
        
 
451
        inDTD = false;
 
452
        if (doctype != null) {
 
453
            doctype.fastSetInternalDTDSubset(internalDTDSubset.toString());
 
454
        }
 
455
        
 
456
    }
 
457
 
 
458
    
 
459
    protected boolean inExternalSubset = false;
 
460
 
 
461
    // We have a problem here. Xerces gets this right,
 
462
    // but Crimson and possibly other parsers don't properly
 
463
    // report these entities, or perhaps just not tag them
 
464
    // with [dtd] like they're supposed to.
 
465
    public void startEntity(String name) {
 
466
      if (name.equals("[dtd]")) inExternalSubset = true;
 
467
    }
 
468
    
 
469
    
 
470
    public void endEntity(String name) {
 
471
      if (name.equals("[dtd]")) inExternalSubset = false;    
 
472
    }
 
473
    
 
474
    
 
475
    protected boolean inCDATA = false;
 
476
    protected boolean finishedCDATA = false;
 
477
    
 
478
    public void startCDATA() {
 
479
        if (textString == null) inCDATA = true;
 
480
        finishedCDATA = false;
 
481
    }
 
482
    
 
483
    
 
484
    public void endCDATA() {
 
485
        finishedCDATA = true;
 
486
    }
 
487
 
 
488
    
 
489
    public void comment(char[] text, int start, int length) {
 
490
        
 
491
        if (!inDTD) flushText();
 
492
        if (inDTD && !inInternalSubset()) return;
 
493
 
 
494
        Nodes result = factory.makeComment(new String(text, start, length));
 
495
        
 
496
        for (int i = 0; i < result.size(); i++) {
 
497
            Node node = result.get(i);
 
498
            if (!inDTD) {
 
499
                if (inProlog) {
 
500
                    parent.insertChild(node, position);
 
501
                    position++;
 
502
                }
 
503
                else {
 
504
                    if (node instanceof Attribute) {
 
505
                        ((Element) parent).addAttribute((Attribute) node);
 
506
                    }
 
507
                    else parent.appendChild(node);
 
508
                }
 
509
            }
 
510
            else {
 
511
                if (node.isComment() || node.isProcessingInstruction()) {
 
512
                    internalDTDSubset.append("  ");            
 
513
                    internalDTDSubset.append(node.toXML());            
 
514
                    internalDTDSubset.append("\n");            
 
515
                }
 
516
                else {
 
517
                    throw new XMLException("Factory tried to put a " 
 
518
                      + node.getClass().getName() 
 
519
                      + " in the internal DTD subset");   
 
520
                }
 
521
            }            
 
522
        }
 
523
 
 
524
    }    
 
525
    
 
526
    
 
527
    public void elementDecl(String name, String model) {
 
528
        
 
529
        if (inInternalSubset() && doctype != null) {
 
530
            internalDTDSubset.append("  <!ELEMENT ");
 
531
            internalDTDSubset.append(name); 
 
532
            internalDTDSubset.append(' '); 
 
533
            internalDTDSubset.append(model); 
 
534
            // workaround for Crimson bug
 
535
            if (model.indexOf("#PCDATA") > 0 && model.indexOf('|') > 0) {
 
536
                if (model.endsWith(")")) {
 
537
                    internalDTDSubset.append('*');  
 
538
                }
 
539
            }
 
540
            internalDTDSubset.append(">\n"); 
 
541
        }
 
542
        
 
543
    }
 
544
  
 
545
    
 
546
    // This method only behaves properly when called from the DeclHandler 
 
547
    // and DTDHandler callbacks; i.e. from inside the DTD;
 
548
    // It is not intended for use anywhere in the document.
 
549
    protected boolean inInternalSubset() {
 
550
 
 
551
        if (!usingCrimson && !inExternalSubset) return true;
 
552
        String currentURI = locator.getSystemId();
 
553
        if (currentURI == this.documentBaseURI) return true;
 
554
        if (currentURI.equals(this.documentBaseURI)) return true;
 
555
        return false;
 
556
        
 
557
    }
 
558
 
 
559
 
 
560
    public void attributeDecl(String elementName, 
 
561
      String attributeName, String type, String mode, 
 
562
      String defaultValue)  {
 
563
    
 
564
        // workaround for Crimson bug
 
565
        if (type.startsWith("NOTATION ")) {
 
566
            if (type.indexOf('(') == -1 && ! type.endsWith(")")) {
 
567
                type = "NOTATION (" + type.substring("NOTATION ".length()) + ")";
 
568
            }
 
569
        }
 
570
        
 
571
        if (inInternalSubset() && doctype != null) {
 
572
            internalDTDSubset.append("  <!ATTLIST ");
 
573
            internalDTDSubset.append(elementName);
 
574
            internalDTDSubset.append(' ');
 
575
            internalDTDSubset.append(attributeName);
 
576
            internalDTDSubset.append(' ');
 
577
            internalDTDSubset.append(type);
 
578
            if (mode != null) {
 
579
            internalDTDSubset.append(' ');
 
580
                internalDTDSubset.append(mode);
 
581
            }
 
582
            if (defaultValue != null) {
 
583
                internalDTDSubset.append(' ');
 
584
                internalDTDSubset.append('"');
 
585
                internalDTDSubset.append(escapeReservedCharactersInDefaultAttributeValues(defaultValue));
 
586
                internalDTDSubset.append("\"");         
 
587
            }
 
588
            internalDTDSubset.append(">\n");   
 
589
        }
 
590
        
 
591
    }
 
592
  
 
593
    
 
594
    public void internalEntityDecl(String name, 
 
595
       String value) {   
 
596
        
 
597
        if (inInternalSubset() && doctype != null) {
 
598
            internalDTDSubset.append("  <!ENTITY ");
 
599
            if (name.startsWith("%")) {
 
600
                internalDTDSubset.append("% "); 
 
601
                internalDTDSubset.append(name.substring(1));
 
602
            }
 
603
            else {
 
604
                internalDTDSubset.append(name); 
 
605
            }
 
606
            internalDTDSubset.append(" \""); 
 
607
            internalDTDSubset.append(escapeReservedCharactersInDeclarations(value)); 
 
608
            internalDTDSubset.append("\">\n"); 
 
609
        }
 
610
        
 
611
    }
 
612
  
 
613
    
 
614
    public void externalEntityDecl(String name, 
 
615
       String publicID, String systemID) {
 
616
        
 
617
        if (inInternalSubset() && doctype != null) {
 
618
            internalDTDSubset.append("  <!ENTITY ");
 
619
            if (name.startsWith("%")) { 
 
620
                internalDTDSubset.append("% ");
 
621
                internalDTDSubset.append(name.substring(1));
 
622
            }
 
623
            else {
 
624
                internalDTDSubset.append(name);
 
625
            }
 
626
               
 
627
            if (locator != null && URIUtil.isAbsolute(systemID)) {
 
628
                String documentURL = locator.getSystemId();
 
629
                // work around Crimson style file:/root URLs
 
630
                if (documentURL != null) {
 
631
                    if (documentURL.startsWith("file:/") && !documentURL.startsWith("file:///")) {
 
632
                        documentURL = "file://" + documentURL.substring(5); 
 
633
                    }
 
634
                    if (systemID.startsWith("file:/") && !systemID.startsWith("file:///")) {
 
635
                        systemID = "file://" + systemID.substring(5); 
 
636
                    }
 
637
                    systemID = URIUtil.relativize(documentURL, systemID);
 
638
                }
 
639
            }
 
640
 
 
641
            if (publicID != null) { 
 
642
                internalDTDSubset.append(" PUBLIC \""); 
 
643
                internalDTDSubset.append(publicID); 
 
644
                internalDTDSubset.append("\" \""); 
 
645
                internalDTDSubset.append(systemID);       
 
646
            }
 
647
            else {
 
648
                // need to escape system ID????
 
649
                internalDTDSubset.append(" SYSTEM \""); 
 
650
                internalDTDSubset.append(systemID); 
 
651
            }
 
652
            internalDTDSubset.append("\">\n");
 
653
            
 
654
        }
 
655
        
 
656
    }
 
657
    
 
658
    
 
659
    public void notationDecl(String name, String publicID, 
 
660
      String systemID) {
 
661
        
 
662
        if (systemID != null) {
 
663
            systemID = escapeReservedCharactersInDeclarations(systemID);
 
664
        }
 
665
        
 
666
        if (inInternalSubset() && doctype != null) {
 
667
            internalDTDSubset.append("  <!NOTATION ");
 
668
            internalDTDSubset.append(name); 
 
669
            if (publicID != null) {
 
670
                internalDTDSubset.append(" PUBLIC \""); 
 
671
                internalDTDSubset.append(publicID);
 
672
                internalDTDSubset.append('"'); 
 
673
                if (systemID != null) {
 
674
                    internalDTDSubset.append(" \"");                                     
 
675
                    internalDTDSubset.append(systemID);
 
676
                    internalDTDSubset.append('"');                                     
 
677
                }
 
678
            }
 
679
            else {
 
680
                internalDTDSubset.append(" SYSTEM \""); 
 
681
                internalDTDSubset.append(systemID);
 
682
                internalDTDSubset.append('"');                 
 
683
            }
 
684
            internalDTDSubset.append(">\n"); 
 
685
        }        
 
686
        
 
687
    }
 
688
   
 
689
    
 
690
    public void unparsedEntityDecl(String name, String publicID, 
 
691
     String systemID, String notationName) {
 
692
        
 
693
        // escapable characters????
 
694
        if (inInternalSubset() && doctype != null) {
 
695
            internalDTDSubset.append("  <!ENTITY ");
 
696
            if (publicID != null) { 
 
697
                internalDTDSubset.append(name); 
 
698
                internalDTDSubset.append(" PUBLIC \""); 
 
699
                internalDTDSubset.append(publicID); 
 
700
                internalDTDSubset.append("\" \""); 
 
701
                internalDTDSubset.append(systemID); 
 
702
                internalDTDSubset.append("\" NDATA "); 
 
703
                internalDTDSubset.append(notationName);       
 
704
            }
 
705
            else {
 
706
                internalDTDSubset.append(name); 
 
707
                internalDTDSubset.append(" SYSTEM \""); 
 
708
                internalDTDSubset.append(systemID); 
 
709
                internalDTDSubset.append("\" NDATA "); 
 
710
                internalDTDSubset.append(notationName);     
 
711
            }
 
712
            internalDTDSubset.append(">\n"); 
 
713
        }
 
714
        
 
715
    }
 
716
    
 
717
    
 
718
    private static String escapeReservedCharactersInDeclarations(String s) {
 
719
        
 
720
        int length = s.length();
 
721
        StringBuffer result = new StringBuffer(length);
 
722
        for (int i = 0; i < length; i++) {
 
723
            char c = s.charAt(i);
 
724
            switch (c) {
 
725
                case '\r': 
 
726
                    result.append("&#x0D;");
 
727
                    break;
 
728
                case 14:
 
729
                    // placeholder for table lookup
 
730
                    break;
 
731
                case 15:
 
732
                    // placeholder for table lookup
 
733
                    break;
 
734
                case 16 :
 
735
                    // placeholder for table lookup
 
736
                    break;
 
737
                case 17:
 
738
                    // placeholder for table lookup
 
739
                    break;
 
740
                case 18:
 
741
                    // placeholder for table lookup
 
742
                    break;
 
743
                case 19:
 
744
                    // placeholder for table lookup
 
745
                    break;
 
746
                case 20:
 
747
                    // placeholder for table lookup
 
748
                    break;
 
749
                case 21:
 
750
                    // placeholder for table lookup
 
751
                    break;
 
752
                case 22:
 
753
                    // placeholder for table lookup
 
754
                    break;
 
755
                case 23:
 
756
                    // placeholder for table lookup
 
757
                    break;
 
758
                case 24:
 
759
                    // placeholder for table lookup
 
760
                    break;
 
761
                case 25:
 
762
                    // placeholder for table lookup
 
763
                    break;
 
764
                case 26:
 
765
                    // placeholder for table lookup
 
766
                    break;
 
767
                case 27:
 
768
                    // placeholder for table lookup
 
769
                    break;
 
770
                case 28:
 
771
                    // placeholder for table lookup
 
772
                    break;
 
773
                case 29:
 
774
                    // placeholder for table lookup
 
775
                    break;
 
776
                case 30:
 
777
                    // placeholder for table lookup
 
778
                    break;
 
779
                case 31:
 
780
                    // placeholder for table lookup
 
781
                    break;
 
782
                case ' ':
 
783
                    result.append(' ');
 
784
                    break;
 
785
                case '!':
 
786
                    result.append('!');
 
787
                    break;
 
788
                case '\"':
 
789
                    result.append("&#x22;");
 
790
                    break;
 
791
                case '#':
 
792
                    result.append('#');
 
793
                    break;
 
794
                case '$':
 
795
                    result.append('$');
 
796
                    break;
 
797
                case '%':
 
798
                    result.append("&#x25;");
 
799
                    break;
 
800
                case '&':
 
801
                    result.append("&#x26;");
 
802
                    break;
 
803
                default:
 
804
                    result.append(c);
 
805
            }
 
806
        }
 
807
        
 
808
        return result.toString();
 
809
        
 
810
    }
 
811
 
 
812
    
 
813
    private static String escapeReservedCharactersInDefaultAttributeValues(String s) {
 
814
        
 
815
        int length = s.length();
 
816
        StringBuffer result = new StringBuffer(length);
 
817
        for (int i = 0; i < length; i++) {
 
818
            char c = s.charAt(i);
 
819
            switch (c) {
 
820
                case '\r': 
 
821
                    result.append("&#x0D;");
 
822
                    break;
 
823
                case 14:
 
824
                    // placeholder for table lookup
 
825
                    break;
 
826
                case 15:
 
827
                    // placeholder for table lookup
 
828
                    break;
 
829
                case 16 :
 
830
                    // placeholder for table lookup
 
831
                    break;
 
832
                case 17:
 
833
                    // placeholder for table lookup
 
834
                    break;
 
835
                case 18:
 
836
                    // placeholder for table lookup
 
837
                    break;
 
838
                case 19:
 
839
                    // placeholder for table lookup
 
840
                    break;
 
841
                case 20:
 
842
                    // placeholder for table lookup
 
843
                    break;
 
844
                case 21:
 
845
                    // placeholder for table lookup
 
846
                    break;
 
847
                case 22:
 
848
                    // placeholder for table lookup
 
849
                    break;
 
850
                case 23:
 
851
                    // placeholder for table lookup
 
852
                    break;
 
853
                case 24:
 
854
                    // placeholder for table lookup
 
855
                    break;
 
856
                case 25:
 
857
                    // placeholder for table lookup
 
858
                    break;
 
859
                case 26:
 
860
                    // placeholder for table lookup
 
861
                    break;
 
862
                case 27:
 
863
                    // placeholder for table lookup
 
864
                    break;
 
865
                case 28:
 
866
                    // placeholder for table lookup
 
867
                    break;
 
868
                case 29:
 
869
                    // placeholder for table lookup
 
870
                    break;
 
871
                case 30:
 
872
                    // placeholder for table lookup
 
873
                    break;
 
874
                case 31:
 
875
                    // placeholder for table lookup
 
876
                    break;
 
877
                case ' ':
 
878
                    result.append(' ');
 
879
                    break;
 
880
                case '!':
 
881
                    result.append('!');
 
882
                    break;
 
883
                case '\"':
 
884
                    result.append("&quot;");
 
885
                    break;
 
886
                case '#':
 
887
                    result.append('#');
 
888
                    break;
 
889
                case '$':
 
890
                    result.append('$');
 
891
                    break;
 
892
                case '%':
 
893
                    result.append("&#x25;");
 
894
                    break;
 
895
                case '&':
 
896
                    result.append("&amp;");
 
897
                    break;
 
898
                case '\'':
 
899
                    result.append('\'');
 
900
                    break;
 
901
                case '(':
 
902
                    result.append('(');
 
903
                    break;
 
904
                case ')':
 
905
                    result.append(')');
 
906
                    break;
 
907
                case '*':
 
908
                    result.append('*');
 
909
                    break;
 
910
                case '+':
 
911
                    result.append('+');
 
912
                    break;
 
913
                case ',':
 
914
                    result.append(',');
 
915
                    break;
 
916
                case '-':
 
917
                    result.append('-');
 
918
                    break;
 
919
                case '.':
 
920
                    result.append('.');
 
921
                    break;
 
922
                case '/':
 
923
                    result.append('/');
 
924
                    break;
 
925
                case '0':
 
926
                    result.append('0');
 
927
                    break;
 
928
                case '1':
 
929
                    result.append('1');
 
930
                    break;
 
931
                case '2':
 
932
                    result.append('2');
 
933
                    break;
 
934
                case '3':
 
935
                    result.append('3');
 
936
                    break;
 
937
                case '4':
 
938
                    result.append('4');
 
939
                    break;
 
940
                case '5':
 
941
                    result.append('5');
 
942
                    break;
 
943
                case '6':
 
944
                    result.append('6');
 
945
                    break;
 
946
                case '7':
 
947
                    result.append('7');
 
948
                    break;
 
949
                case '8':
 
950
                    result.append('8');
 
951
                    break;
 
952
                case '9':
 
953
                    result.append('9');
 
954
                    break;
 
955
                case ':':
 
956
                    result.append(':');
 
957
                    break;
 
958
                case ';':
 
959
                    result.append(';');
 
960
                    break;
 
961
                case '<': 
 
962
                    result.append("&lt;");
 
963
                    break;
 
964
                default:
 
965
                    result.append(c);
 
966
            }
 
967
        }
 
968
        
 
969
        return result.toString();
 
970
        
 
971
    }
 
972
 
 
973
    
 
974
}
 
 
b'\\ No newline at end of file'