001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.dna.graph.xml;
023    
024    import java.io.InputStream;
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.LinkedList;
028    import java.util.List;
029    import java.util.Map;
030    import org.jboss.dna.common.monitor.ProgressMonitor;
031    import org.jboss.dna.common.util.StringUtil;
032    import org.jboss.dna.graph.GraphI18n;
033    import org.jboss.dna.graph.properties.Name;
034    import org.jboss.dna.graph.properties.NameFactory;
035    import org.jboss.dna.graph.properties.NamespaceRegistry;
036    import org.jboss.dna.graph.properties.Path;
037    import org.jboss.dna.graph.sequencers.SequencerContext;
038    import org.jboss.dna.graph.sequencers.SequencerOutput;
039    import org.jboss.dna.graph.sequencers.StreamSequencer;
040    import org.xml.sax.Attributes;
041    import org.xml.sax.InputSource;
042    import org.xml.sax.SAXException;
043    import org.xml.sax.SAXNotRecognizedException;
044    import org.xml.sax.SAXNotSupportedException;
045    import org.xml.sax.SAXParseException;
046    import org.xml.sax.XMLReader;
047    import org.xml.sax.ext.DefaultHandler2;
048    import org.xml.sax.helpers.XMLReaderFactory;
049    
050    /**
051     * @author John Verhaeg
052     */
053    public class XmlSequencer implements StreamSequencer {
054    
055        private static final String DEFAULT_PRIMARY_TYPE = "nt:unstructured";
056        private static final String DECL_HANDLER_FEATURE = "http://xml.org/sax/properties/declaration-handler";
057        private static final String ENTITY_RESOLVER_2_FEATURE = "http://xml.org/sax/features/use-entity-resolver2";
058        private static final String LEXICAL_HANDLER_FEATURE = "http://xml.org/sax/properties/lexical-handler";
059        private static final String RESOLVE_DTD_URIS_FEATURE = "http://xml.org/sax/features/resolve-dtd-uris";
060        private static final String LOAD_EXTERNAL_DTDS_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
061    
062        /**
063         * {@inheritDoc}
064         * 
065         * @see org.jboss.dna.graph.sequencers.StreamSequencer#sequence(InputStream, SequencerOutput, SequencerContext,
066         *      ProgressMonitor)
067         */
068        public void sequence( InputStream stream,
069                              SequencerOutput output,
070                              SequencerContext context,
071                              ProgressMonitor monitor ) {
072            monitor.beginTask(100.0, GraphI18n.sequencingXmlDocument);
073            XMLReader reader;
074            try {
075                reader = XMLReaderFactory.createXMLReader();
076                Handler handler = new Handler(output, context, monitor);
077                reader.setContentHandler(handler);
078                reader.setErrorHandler(handler);
079                // Ensure handler acting as entity resolver 2
080                reader.setProperty(DECL_HANDLER_FEATURE, handler);
081                // Ensure handler acting as lexical handler
082                reader.setProperty(LEXICAL_HANDLER_FEATURE, handler);
083                // Ensure handler acting as entity resolver 2
084                setFeature(reader, ENTITY_RESOLVER_2_FEATURE, true);
085                // Prevent loading of external DTDs
086                setFeature(reader, LOAD_EXTERNAL_DTDS_FEATURE, false);
087                // Prevent the resolving of DTD entities into fully-qualified URIS
088                setFeature(reader, RESOLVE_DTD_URIS_FEATURE, false);
089                // Parse XML document
090                reader.parse(new InputSource(stream));
091            } catch (Exception error) {
092                context.getLogger(getClass()).error(error, GraphI18n.fatalErrorSequencingXmlDocument, error);
093                monitor.getProblems().addError(error, GraphI18n.fatalErrorSequencingXmlDocument, error);
094            } finally {
095                monitor.done();
096            }
097        }
098    
099        /**
100         * Sets the reader's named feature to the supplied value, only if the feature is not already set to that value. This method
101         * does nothing if the feature is not known to the reader.
102         * 
103         * @param reader the reader; may not be null
104         * @param featureName the name of the feature; may not be null
105         * @param value the value for the feature
106         */
107        private void setFeature( XMLReader reader,
108                                 String featureName,
109                                 boolean value ) {
110            try {
111                if (reader.getFeature(featureName) != value) {
112                    reader.setFeature(featureName, value);
113                }
114            } catch (SAXNotRecognizedException meansFeatureNotRecognized) {
115            } catch (SAXNotSupportedException meansFeatureNotSupported) {
116            }
117        }
118    
119        private final class Handler extends DefaultHandler2 {
120    
121            private final SequencerOutput output;
122            private final SequencerContext context;
123            private final ProgressMonitor monitor;
124    
125            private double progress;
126    
127            private Path path; // The DNA path of the node representing the current XML element
128    
129            // Cached instances of the name factory and commonly referenced names
130            private final NameFactory nameFactory;
131            private Name primaryTypeName;
132            private Name defaultPrimaryType;
133    
134            // Recursive map used to track the number of occurrences of names for elements under a particular path
135            private Map<Name, List<IndexedName>> nameToIndexedNamesMap = new HashMap<Name, List<IndexedName>>();
136    
137            // The stack of recursive maps being processed, with the head entry being the map for the current path
138            private final LinkedList<Map<Name, List<IndexedName>>> nameToIndexedNamesMapStack = new LinkedList<Map<Name, List<IndexedName>>>();
139    
140            // The stack of XML namespace in scope, with the head entry being namespace of the closest ancestor element declaring a
141            // namespace.
142            private final LinkedList<String> nsStack = new LinkedList<String>();
143    
144            // Builder used to concatenate concurrent lines of CDATA into a single value.
145            private StringBuilder cDataBuilder;
146    
147            // Builder used to concatenate concurrent lines of element content and entity evaluations into a single value.
148            private StringBuilder contentBuilder;
149    
150            // The entity being processed
151            private String entity;
152    
153            Handler( SequencerOutput output,
154                     SequencerContext context,
155                     ProgressMonitor monitor ) {
156                assert output != null;
157                assert monitor != null;
158                assert context != null;
159                this.output = output;
160                this.context = context;
161                this.monitor = monitor;
162                // Initialize path to a an empty path relative to the SequencerOutput's target path.
163                path = context.getFactories().getPathFactory().createRelativePath();
164                // Cache name factory since it is frequently used
165                nameFactory = context.getFactories().getNameFactory();
166            }
167    
168            /**
169             * <p>
170             * {@inheritDoc}
171             * </p>
172             * 
173             * @see org.xml.sax.ext.DefaultHandler2#attributeDecl(java.lang.String, java.lang.String, java.lang.String,
174             *      java.lang.String, java.lang.String)
175             */
176            @Override
177            public void attributeDecl( String name,
178                                       String name2,
179                                       String type,
180                                       String mode,
181                                       String value ) throws SAXException {
182                stopIfCancelled();
183            }
184    
185            /**
186             * <p>
187             * {@inheritDoc}
188             * </p>
189             * 
190             * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
191             */
192            @Override
193            public void characters( char[] ch,
194                                    int start,
195                                    int length ) throws SAXException {
196                stopIfCancelled();
197                String content = String.valueOf(ch, start, length);
198                // Check if data should be appended to previously parsed CDATA
199                if (cDataBuilder == null) {
200                    // If content is for an entity, replace with entity reference
201                    if (entity != null) {
202                        content = '&' + entity + ';';
203                    }
204                    // Check if first line of content
205                    if (contentBuilder == null) {
206                        contentBuilder = new StringBuilder(content);
207                    } else {
208                        // Append additional lines or entity evaluations to previous content, separated by a space
209                        if (entity == null) {
210                            contentBuilder.append(' ');
211                        }
212                        contentBuilder.append(content);
213                        // Text within builder will be output when another element or CDATA is encountered
214                    }
215                } else {
216                    cDataBuilder.append(ch, start, length);
217                    // Text within builder will be output at the end of CDATA
218                }
219                updateProgress();
220            }
221    
222            /**
223             * <p>
224             * {@inheritDoc}
225             * </p>
226             * 
227             * @see org.xml.sax.ext.DefaultHandler2#comment(char[], int, int)
228             */
229            @Override
230            public void comment( char[] ch,
231                                 int start,
232                                 int length ) throws SAXException {
233                stopIfCancelled();
234                // Output separate nodes for each comment since multiple are allowed
235                startElement(DnaXmlLexicon.COMMENT);
236                output.setProperty(path, getPrimaryTypeName(), DnaXmlLexicon.COMMENT);
237                output.setProperty(path, DnaXmlLexicon.COMMENT_CONTENT, String.valueOf(ch, start, length));
238                endElement();
239                updateProgress();
240            }
241    
242            /**
243             * <p>
244             * {@inheritDoc}
245             * </p>
246             * 
247             * @see org.xml.sax.ext.DefaultHandler2#elementDecl(java.lang.String, java.lang.String)
248             */
249            @Override
250            public void elementDecl( String name,
251                                     String model ) throws SAXException {
252                stopIfCancelled();
253            }
254    
255            /**
256             * <p>
257             * {@inheritDoc}
258             * </p>
259             * 
260             * @see org.xml.sax.ext.DefaultHandler2#endCDATA()
261             */
262            @Override
263            public void endCDATA() throws SAXException {
264                stopIfCancelled();
265                // Output CDATA built in characters() method
266                output.setProperty(path, DnaXmlLexicon.CDATA_CONTENT, cDataBuilder.toString());
267                endElement();
268                // Null-out builder to free memory
269                cDataBuilder = null;
270                updateProgress();
271            }
272    
273            private void endContent() {
274                if (contentBuilder != null) {
275                    // Normalize content
276                    String content = StringUtil.normalize(contentBuilder.toString());
277                    // Null-out builder to setup for subsequent content.
278                    // Must be done before call to startElement below to prevent infinite loop.
279                    contentBuilder = null;
280                    // Skip if nothing in content but whitespace
281                    if (content.length() > 0) {
282                        // Create separate node for each content entry since entries can be interspersed amongst child elements
283                        startElement(DnaXmlLexicon.ELEMENT_CONTENT);
284                        output.setProperty(path, getPrimaryTypeName(), DnaXmlLexicon.ELEMENT_CONTENT);
285                        output.setProperty(path, DnaXmlLexicon.ELEMENT_CONTENT, content);
286                        endElement();
287                    }
288                }
289            }
290    
291            /**
292             * <p>
293             * {@inheritDoc}
294             * </p>
295             * 
296             * @see org.xml.sax.helpers.DefaultHandler#endDocument()
297             */
298            @Override
299            public void endDocument() throws SAXException {
300                stopIfCancelled();
301            }
302    
303            /**
304             * <p>
305             * {@inheritDoc}
306             * </p>
307             * 
308             * @see org.xml.sax.ext.DefaultHandler2#endDTD()
309             */
310            @Override
311            public void endDTD() throws SAXException {
312                stopIfCancelled();
313            }
314    
315            private void endElement() {
316                // Recover parent's path, namespace, and indexedName map, clearing the ended element's map to free memory
317                path = path.getParent();
318                nameToIndexedNamesMap.clear();
319                nameToIndexedNamesMap = nameToIndexedNamesMapStack.removeFirst();
320                nsStack.removeFirst();
321            }
322    
323            /**
324             * <p>
325             * {@inheritDoc}
326             * </p>
327             * 
328             * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
329             */
330            @Override
331            public void endElement( String uri,
332                                    String localName,
333                                    String name ) throws SAXException {
334                stopIfCancelled();
335                // Check if content still needs to be output
336                endContent();
337                endElement();
338                updateProgress();
339            }
340    
341            /**
342             * <p>
343             * {@inheritDoc}
344             * </p>
345             * 
346             * @see org.xml.sax.ext.DefaultHandler2#endEntity(java.lang.String)
347             */
348            @Override
349            public void endEntity( String name ) throws SAXException {
350                stopIfCancelled();
351                entity = null;
352                updateProgress();
353            }
354    
355            /**
356             * <p>
357             * {@inheritDoc}
358             * </p>
359             * 
360             * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException)
361             */
362            @Override
363            public void error( SAXParseException error ) {
364                context.getLogger(XmlSequencer.class).error(error, GraphI18n.errorSequencingXmlDocument, error);
365                monitor.getProblems().addError(error, GraphI18n.errorSequencingXmlDocument, error);
366            }
367    
368            /**
369             * <p>
370             * {@inheritDoc}
371             * </p>
372             * 
373             * @see org.xml.sax.ext.DefaultHandler2#externalEntityDecl(java.lang.String, java.lang.String, java.lang.String)
374             */
375            @Override
376            public void externalEntityDecl( String name,
377                                            String publicId,
378                                            String systemId ) throws SAXException {
379                stopIfCancelled();
380                // Add "synthetic" entity container to path to help prevent name collisions with XML elements
381                Name entityName = DnaDtdLexicon.ENTITY;
382                startElement(entityName);
383                output.setProperty(path, getPrimaryTypeName(), entityName);
384                output.setProperty(path, nameFactory.create(DnaDtdLexicon.NAME), name);
385                output.setProperty(path, nameFactory.create(DnaDtdLexicon.PUBLIC_ID), publicId);
386                output.setProperty(path, nameFactory.create(DnaDtdLexicon.SYSTEM_ID), systemId);
387                endElement();
388                updateProgress();
389            }
390    
391            /**
392             * <p>
393             * {@inheritDoc}
394             * </p>
395             * 
396             * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException)
397             */
398            @Override
399            public void fatalError( SAXParseException error ) {
400                context.getLogger(XmlSequencer.class).error(error, GraphI18n.fatalErrorSequencingXmlDocument, error);
401                monitor.getProblems().addError(error, GraphI18n.fatalErrorSequencingXmlDocument, error);
402            }
403    
404            private Name getPrimaryTypeName() {
405                if (primaryTypeName == null) {
406                    primaryTypeName = nameFactory.create(NameFactory.JCR_PRIMARY_TYPE);
407                }
408                return primaryTypeName;
409            }
410    
411            private Name getDefaultPrimaryType() {
412                if (defaultPrimaryType == null) {
413                    defaultPrimaryType = nameFactory.create(DEFAULT_PRIMARY_TYPE);
414                }
415                return defaultPrimaryType;
416            }
417    
418            /**
419             * <p>
420             * {@inheritDoc}
421             * </p>
422             * 
423             * @see org.xml.sax.helpers.DefaultHandler#ignorableWhitespace(char[], int, int)
424             */
425            @Override
426            public void ignorableWhitespace( char[] ch,
427                                             int start,
428                                             int length ) throws SAXException {
429                stopIfCancelled();
430            }
431    
432            /**
433             * <p>
434             * {@inheritDoc}
435             * </p>
436             * 
437             * @see org.xml.sax.ext.DefaultHandler2#internalEntityDecl(java.lang.String, java.lang.String)
438             */
439            @Override
440            public void internalEntityDecl( String name,
441                                            String value ) throws SAXException {
442                stopIfCancelled();
443                // Add "synthetic" entity container to path to help prevent name collisions with XML elements
444                Name entityName = DnaDtdLexicon.ENTITY;
445                startElement(entityName);
446                output.setProperty(path, getPrimaryTypeName(), entityName);
447                output.setProperty(path, DnaDtdLexicon.NAME, name);
448                output.setProperty(path, DnaDtdLexicon.VALUE, value);
449                endElement();
450                updateProgress();
451            }
452    
453            /**
454             * <p>
455             * {@inheritDoc}
456             * </p>
457             * 
458             * @see org.xml.sax.helpers.DefaultHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String)
459             */
460            @Override
461            public void notationDecl( String name,
462                                      String publicId,
463                                      String systemId ) throws SAXException {
464                stopIfCancelled();
465            }
466    
467            /**
468             * <p>
469             * {@inheritDoc}
470             * </p>
471             * 
472             * @see org.xml.sax.helpers.DefaultHandler#processingInstruction(java.lang.String, java.lang.String)
473             */
474            @Override
475            public void processingInstruction( String target,
476                                               String data ) throws SAXException {
477                stopIfCancelled();
478                // Output separate nodes for each instruction since multiple are allowed
479                Name name = DnaXmlLexicon.PROCESSING_INSTRUCTION;
480                startElement(name);
481                output.setProperty(path, getPrimaryTypeName(), name);
482                output.setProperty(path, DnaXmlLexicon.TARGET, target);
483                output.setProperty(path, DnaXmlLexicon.PROCESSING_INSTRUCTION_CONTENT, data);
484                endElement();
485                updateProgress();
486            }
487    
488            /**
489             * <p>
490             * {@inheritDoc}
491             * </p>
492             * 
493             * @see org.xml.sax.helpers.DefaultHandler#skippedEntity(java.lang.String)
494             */
495            @Override
496            public void skippedEntity( String name ) throws SAXException {
497                stopIfCancelled();
498            }
499    
500            /**
501             * <p>
502             * {@inheritDoc}
503             * </p>
504             * 
505             * @see org.xml.sax.ext.DefaultHandler2#startCDATA()
506             */
507            @Override
508            public void startCDATA() throws SAXException {
509                stopIfCancelled();
510                // Output separate nodes for each CDATA since multiple are allowed
511                startElement(DnaXmlLexicon.CDATA);
512                // Prepare builder for concatenating consecutive lines of CDATA
513                cDataBuilder = new StringBuilder();
514                updateProgress();
515            }
516    
517            /**
518             * <p>
519             * {@inheritDoc}
520             * </p>
521             * 
522             * @see org.xml.sax.helpers.DefaultHandler#startDocument()
523             */
524            @Override
525            public void startDocument() throws SAXException {
526                stopIfCancelled();
527                output.setProperty(path, getPrimaryTypeName(), DnaXmlLexicon.DOCUMENT);
528                updateProgress();
529            }
530    
531            /**
532             * <p>
533             * {@inheritDoc}
534             * </p>
535             * 
536             * @see org.xml.sax.ext.DefaultHandler2#startDTD(java.lang.String, java.lang.String, java.lang.String)
537             */
538            @Override
539            public void startDTD( String name,
540                                  String publicId,
541                                  String systemId ) throws SAXException {
542                stopIfCancelled();
543                output.setProperty(path, DnaDtdLexicon.NAME, name);
544                output.setProperty(path, DnaDtdLexicon.PUBLIC_ID, publicId);
545                output.setProperty(path, DnaDtdLexicon.SYSTEM_ID, systemId);
546                updateProgress();
547            }
548    
549            private void startElement( Name name ) {
550                // Check if content still needs to be output
551                endContent();
552                // Add name to list of indexed names for this element to ensure we use the correct index (which is the size of the
553                // list)
554                List<IndexedName> indexedNames = nameToIndexedNamesMap.get(name);
555                if (indexedNames == null) {
556                    indexedNames = new ArrayList<IndexedName>();
557                    nameToIndexedNamesMap.put(name, indexedNames);
558                }
559                IndexedName indexedName = new IndexedName();
560                indexedNames.add(indexedName);
561                // Add element name and the appropriate index to the path.
562                // Per the JCR spec, the index must be relative to same-name sibling nodes
563                path = context.getFactories().getPathFactory().create(path, name, indexedNames.size());
564                path = path.getNormalizedPath();
565                // Add the indexed name map to the stack and set the current map to the new element's map
566                nameToIndexedNamesMapStack.addFirst(nameToIndexedNamesMap);
567                nameToIndexedNamesMap = indexedName.nameToIndexedNamesMap;
568                // Set the current namespace to whatever is declared by this element, or if not declared, to its nearest ancestor that
569                // does declare a namespace.
570                String ns = name.getNamespaceUri();
571                if (ns.length() == 0) {
572                    nsStack.addFirst(nsStack.isEmpty() ? "" : nsStack.getFirst());
573                } else {
574                    nsStack.addFirst(ns);
575                }
576            }
577    
578            /**
579             * <p>
580             * {@inheritDoc}
581             * </p>
582             * 
583             * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
584             *      org.xml.sax.Attributes)
585             */
586            @Override
587            public void startElement( String uri,
588                                      String localName,
589                                      String name,
590                                      Attributes attributes ) throws SAXException {
591                stopIfCancelled();
592                // Look for the "jcr:name" attribute, and use that if it's there
593                Name type = getDefaultPrimaryType();
594                Name nameObj = nameFactory.create(name);
595                for (int ndx = 0, len = attributes.getLength(); ndx < len; ++ndx) {
596                    String ns = attributes.getURI(ndx);
597                    String attrLocalName = attributes.getLocalName(ndx);
598                    Object value = attributes.getValue(ndx);
599                    String jcrNsUri = context.getNamespaceRegistry().getNamespaceForPrefix("jcr");
600                    if (jcrNsUri != null && jcrNsUri.equals(ns) && attrLocalName.equals("name")) {
601                        nameObj = nameFactory.create(value);
602                        break;
603                    }
604                }
605                startElement(nameObj);
606                output.setProperty(path, getPrimaryTypeName(), type);
607                // Output this element's attributes using the attribute's namespace, if supplied, or the current namespace in scope.
608                String inheritedNs = nsStack.getFirst();
609                for (int ndx = 0, len = attributes.getLength(); ndx < len; ++ndx) {
610                    String ns = attributes.getURI(ndx);
611                    String attrLocalName = attributes.getLocalName(ndx);
612                    Object value = attributes.getValue(ndx);
613                    String jcrNsUri = context.getNamespaceRegistry().getNamespaceForPrefix("jcr");
614                    if (jcrNsUri != null && jcrNsUri.equals(ns) && attrLocalName.equals("primaryType")) {
615                        value = nameFactory.create(value);
616                    }
617                    if (jcrNsUri != null && jcrNsUri.equals(ns) && attrLocalName.equals("name")) {
618                        continue;
619                    }
620                    output.setProperty(path, nameFactory.create(ns.length() == 0 ? inheritedNs : ns, attrLocalName), value);
621                }
622                updateProgress();
623            }
624    
625            /**
626             * <p>
627             * {@inheritDoc}
628             * </p>
629             * 
630             * @see org.xml.sax.ext.DefaultHandler2#startEntity(java.lang.String)
631             */
632            @Override
633            public void startEntity( String name ) throws SAXException {
634                stopIfCancelled();
635                entity = name;
636                updateProgress();
637            }
638    
639            /**
640             * <p>
641             * {@inheritDoc}
642             * </p>
643             * 
644             * @see org.xml.sax.helpers.DefaultHandler#startPrefixMapping(java.lang.String, java.lang.String)
645             */
646            @Override
647            public void startPrefixMapping( String prefix,
648                                            String uri ) throws SAXException {
649                stopIfCancelled();
650                // Register any unregistered namespaces
651                NamespaceRegistry registry = context.getNamespaceRegistry();
652                if (!registry.isRegisteredNamespaceUri(uri)) {
653                    registry.register(prefix, uri);
654                }
655                updateProgress();
656            }
657    
658            private void stopIfCancelled() throws SAXException {
659                if (monitor.isCancelled()) {
660                    throw new SAXException(GraphI18n.canceledSequencingXmlDocument.text());
661                }
662            }
663    
664            /**
665             * <p>
666             * {@inheritDoc}
667             * </p>
668             * 
669             * @see org.xml.sax.helpers.DefaultHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String,
670             *      java.lang.String)
671             */
672            @Override
673            public void unparsedEntityDecl( String name,
674                                            String publicId,
675                                            String systemId,
676                                            String notationName ) throws SAXException {
677                stopIfCancelled();
678            }
679    
680            private void updateProgress() {
681                if (progress == 100.0) {
682                    progress = 1;
683                } else {
684                    progress++;
685                }
686                monitor.worked(progress);
687            }
688    
689            /**
690             * <p>
691             * {@inheritDoc}
692             * </p>
693             * 
694             * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException)
695             */
696            @Override
697            public void warning( SAXParseException warning ) {
698                context.getLogger(XmlSequencer.class).warn(warning, GraphI18n.warningSequencingXmlDocument);
699                monitor.getProblems().addWarning(warning, GraphI18n.warningSequencingXmlDocument, warning);
700            }
701        }
702    
703        private class IndexedName {
704    
705            Map<Name, List<IndexedName>> nameToIndexedNamesMap = new HashMap<Name, List<IndexedName>>();
706    
707            IndexedName() {
708            }
709        }
710    }