2 * This file is part of the LibreOffice project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 * This file incorporates work covered by the following license notice:
10 * Licensed to the Apache Software Foundation (ASF) under one or more
11 * contributor license agreements. See the NOTICE file distributed
12 * with this work for additional information regarding copyright
13 * ownership. The ASF licenses this file to you under the Apache
14 * License, Version 2.0 (the "License"); you may not use this file
15 * except in compliance with the License. You may obtain a copy of
16 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 import java
.io
.PrintWriter
;
22 import java
.util
.ArrayList
;
23 import java
.util
.HashMap
;
24 import java
.util
.HashSet
;
25 import java
.util
.Iterator
;
27 import com
.sun
.star
.beans
.PropertyValue
;
28 import com
.sun
.star
.xml
.sax
.XAttributeList
;
29 import com
.sun
.star
.xml
.sax
.XDocumentHandler
;
30 import com
.sun
.star
.xml
.sax
.XLocator
;
33 public class XMLTools
{
36 * The implementation of <code>com.sun.star.xml.sax.XAttributeList</code>
37 * where attributes and their values can be added.
39 public static class AttributeList
implements XAttributeList
{
40 private static class Attribute
{
45 private final HashMap
<String
, Attribute
> attrByName
= new HashMap
<String
, Attribute
>() ;
46 private final ArrayList
<Attribute
> attributes
= new ArrayList
<Attribute
>() ;
49 * Creates a class instance.
51 public AttributeList() {}
53 private AttributeList(XAttributeList list
) {
54 if (list
== null) return ;
55 for (short i
= 0; i
< list
.getLength(); i
++) {
56 add(list
.getNameByIndex(i
), list
.getTypeByIndex(i
),
57 list
.getValueByIndex(i
)) ;
62 * Adds an attribute with type and value specified.
63 * @param name The attribute name.
64 * @param type Value type (usually 'CDATA' used).
65 * @param value Attribute value.
67 public void add(String name
, String type
, String value
) {
68 Attribute attr
= new Attribute() ;
72 attributes
.add(attr
) ;
73 attrByName
.put(attr
.Name
, attr
) ;
80 /***************************************
81 * XAttributeList methods
82 ****************************************/
84 public short getLength() {
85 return (short) attributes
.size() ;
88 public String
getNameByIndex(short idx
) {
89 String name
= attributes
.get(idx
).Name
;
93 public String
getTypeByIndex(short idx
) {
94 String type
= attributes
.get(idx
).Type
;
98 public String
getTypeByName(String name
) {
99 String type
= attrByName
.get(name
).Type
;
102 public String
getValueByIndex(short idx
) {
103 String value
= attributes
.get(idx
).Value
;
107 public String
getValueByName(String name
) {
108 String value
= attrByName
.get(name
).Value
;
114 * This class writes all XML data handled into a stream specified
115 * in the constructor.
117 private static class XMLWriter
implements XDocumentHandler
{
118 private PrintWriter _log
= null ;
119 private String align
= "" ;
122 * Creates a SAX handler which writes all XML data
123 * handled into a <code>log</code> stream specified.
125 private XMLWriter(PrintWriter log
) {
130 * Creates a SAX handler which does nothing.
135 public void processingInstruction(String appl
, String data
) {
136 if (_log
== null) return ;
137 _log
.println(align
+ "<?" + appl
+ " " + data
+ "?>") ;
139 public void startDocument() {
140 if (_log
== null) return ;
141 _log
.println("START DOCUMENT:") ;
143 public void endDocument() {
144 if (_log
== null) return ;
145 _log
.println("END DOCUMENT:") ;
147 public void setDocumentLocator(XLocator loc
) {
148 if (_log
== null) return ;
149 _log
.println("DOCUMENT LOCATOR: ('" + loc
.getPublicId() +
150 "','" + loc
.getSystemId() + "')") ;
152 public void startElement(String name
, XAttributeList attr
) {
153 if (_log
== null) return ;
154 _log
.print(align
+ "<" + name
+ " ") ;
156 short attrLen
= attr
.getLength() ;
157 for (short i
= 0; i
< attrLen
; i
++) {
158 if (i
!= 0) _log
.print(align
+ " ") ;
159 _log
.print(attr
.getNameByIndex(i
) + "[" +
160 attr
.getTypeByIndex(i
) + "]=\"" +
161 attr
.getValueByIndex(i
) + "\"") ;
162 if (i
+1 != attrLen
) {
172 public void endElement(String name
) {
173 if (_log
== null) return ;
174 align
= align
.substring(3) ;
175 _log
.println(align
+ "</" + name
+ ">") ;
178 public void characters(String chars
) {
179 if (_log
== null) return ;
180 _log
.println(align
+ chars
) ;
182 public void ignorableWhitespace(String sp
) {
183 if (_log
== null) return ;
189 * Checks if the XML structure is well formed (i.e. all tags opened must be
190 * closed and all tags opened inside a tag must be closed
191 * inside the same tag). It also checks parameters passed.
192 * If any collisions found appropriate error message is
193 * output into a stream specified. No XML data output, i.e.
194 * no output will be performed if no errors occur.<p>
195 * After document is completed there is a way to check
196 * if the XML data and structure was valid.
198 private static class XMLWellFormChecker
extends XMLWriter
{
199 private boolean docStarted
= false ;
200 private boolean docEnded
= false ;
201 ArrayList
<String
> tagStack
= new ArrayList
<String
>() ;
202 private boolean wellFormed
= true ;
203 private boolean noOtherErrors
= true ;
204 PrintWriter log
= null ;
205 private boolean printXMLData
= false ;
207 private XMLWellFormChecker(PrintWriter log
) {
212 private XMLWellFormChecker(PrintWriter log_
, boolean printXMLData
) {
213 super(printXMLData ? log_
: null) ;
214 this.printXMLData
= printXMLData
;
219 * Reset all values. This is important e.g. for test of XFilter
220 * interface, where 'filter()' method is started twice.
225 tagStack
= new ArrayList
<String
>() ;
227 noOtherErrors
= true ;
228 printXMLData
= false ;
232 public void startDocument() {
233 super.startDocument();
236 printError("Document is started twice.") ;
243 public void endDocument() {
247 printError("Document ended but not started.") ;
252 public void startElement(String name
, XAttributeList attr
) {
253 super.startElement(name
, attr
);
255 printError("attribute list passed as parameter to startElement()"+
256 " method has null value for tag <" + name
+ ">") ;
257 noOtherErrors
= false ;
259 tagStack
.add(0, name
) ;
262 public void endElement(String name
) {
263 super.endElement(name
);
265 if (tagStack
.isEmpty()) {
267 printError("No tags to close (bad closing tag </" + name
+ ">)") ;
269 String startTag
= tagStack
.get(0) ;
271 if (!startTag
.equals(name
)) {
273 printError("Bad closing tag: </" + name
+
274 ">; tag expected: </" + startTag
+ ">");
281 * Checks if there were no errors during document handling.
282 * I.e. startDocument() and endDocument() must be called,
283 * XML must be well formed, parameters must be valid.
285 public boolean isWellFormed() {
287 printError("Document was not ended.") ;
291 return wellFormed
&& noOtherErrors
;
295 * Prints error message and all tags where error occurred inside.
296 * Also prints "Tag trace" in case if the full XML data isn't
299 void printError(String msg
) {
300 log
.println("!!! Error: " + msg
) ;
301 if (printXMLData
) return ;
302 log
.println(" Tag trace :") ;
303 for (int i
= 0; i
< tagStack
.size(); i
++) {
304 String tag
= tagStack
.get(i
) ;
305 log
.println(" <" + tag
+ ">") ;
311 * Beside structure of XML this class also can check existence
312 * of tags, inner tags, and character data. After document
313 * completion there is a way to check if required tags and
314 * character data was found. If there any error occurs an
315 * appropriate message is output.
317 public static class XMLTagsChecker
extends XMLWellFormChecker
{
318 private final HashMap
<String
,String
> tags
= new HashMap
<String
,String
>() ;
319 private final HashMap
<String
,String
> chars
= new HashMap
<String
,String
>() ;
320 private boolean allOK
= true ;
322 public XMLTagsChecker(PrintWriter log
) {
327 * Adds a tag name which must be contained in the XML data.
329 public void addTag(String tag
) {
333 * Adds a tag name which must be contained in the XML data and
334 * must be inside the tag with name <code>outerTag</code>.
336 public void addTagEnclosed(String tag
, String outerTag
) {
337 tags
.put(tag
, outerTag
) ;
341 * Adds a character data which must be contained in the XML data and
342 * must be inside the tag with name <code>outerTag</code>.
344 public void addCharactersEnclosed(String ch
, String outerTag
) {
345 chars
.put(ch
, outerTag
) ;
349 public void startElement(String name
, XAttributeList attrs
) {
350 super.startElement(name
, attrs
) ;
351 if (tags
.containsKey(name
)) {
352 String outerTag
= tags
.get(name
);
353 if (outerTag
.length() != 0) {
354 boolean isInTag
= false ;
355 for (int i
= 0; i
< tagStack
.size(); i
++) {
356 if (outerTag
.equals(tagStack
.get(i
))) {
362 printError("Required tag <" + name
+ "> found, but is not enclosed in tag <" +
372 public void characters(String ch
) {
373 super.characters(ch
) ;
375 if (chars
.containsKey(ch
)) {
376 String outerTag
= chars
.get(ch
);
377 if (outerTag
.length() != 0) {
378 boolean isInTag
= false ;
379 for (int i
= 0; i
< tagStack
.size(); i
++) {
380 if (outerTag
.equals(tagStack
.get(i
))) {
386 printError("Required characters '" + ch
+ "' found, but are not enclosed in tag <" +
396 * Checks if the XML data was valid and well formed and if
397 * all necessary tags and character data was found.
399 public boolean checkTags() {
403 Iterator
<String
> badTags
= tags
.keySet().iterator() ;
404 Iterator
<String
> badChars
= chars
.keySet().iterator() ;
406 if (badTags
.hasNext()) {
408 log
.println("Required tags were not found in export :") ;
409 while(badTags
.hasNext()) {
410 log
.println(" <" + badTags
.next() + ">") ;
413 if (badChars
.hasNext()) {
415 log
.println("Required characters were not found in export :") ;
416 while(badChars
.hasNext()) {
417 log
.println(" <" + badChars
.next() + ">") ;
426 * Represents an XML tag which must be found in XML data written.
427 * This tag can contain only its name or tag name and attribute
428 * name, or attribute value additionally.
430 public static class Tag
{
431 private final String name
;
432 private String
[][] attrList
= new String
[0][3] ;
435 * Creates tag which has only a name. Attributes don't make sense.
436 * @param tagName The name of the tag.
438 public Tag(String tagName
) {
443 * Creates a tag with the name specified, which must have an
444 * attribute with the value specified. The type of value
445 * assumed to be 'CDATA'.
446 * @param tagName The name of the tag.
447 * @param attrName The name of attribute which must be contained
449 * @param attrValue Attribute value.
451 public Tag(String tagName
, String attrName
, String attrValue
) {
453 attrList
= new String
[1][3] ;
454 attrList
[0][0] = attrName
;
455 attrList
[0][1] = "CDATA" ;
456 attrList
[0][2] = attrValue
;
460 * Gets tag String description.
463 public String
toString() {
464 StringBuffer ret
= new StringBuffer("<" + name
);
465 for (int i
= 0; i
< attrList
.length
; i
++) {
466 ret
.append(" ").append(attrList
[i
][0]).append("=");
467 if (attrList
[i
][2] == null) {
468 ret
.append("(not specified)");
470 ret
.append("\"").append(attrList
[i
][2]).append("\"");
475 return ret
.toString();
478 private boolean checkAttr(int attrListIdx
, XAttributeList list
) {
480 int listLen
= list
.getLength();
482 if (attrList
[attrListIdx
][0].equals(list
.getNameByIndex(j
))) {
483 if (attrList
[attrListIdx
][2] == null) return true ;
484 return attrList
[attrListIdx
][2].equals(list
.getValueByIndex(j
)) ;
492 * Checks if this tag matches tag passed in parameters.
493 * I.e. if tag specifies only its name it matches if names
494 * are equal (attributes don't make sense). If there are
495 * some attributes names specified in this tag method checks
496 * if all names present in attribute list <code>list</code>
497 * (attributes' values don't make sense). If attributes specified
498 * with values method checks if these attributes exist and
499 * have appropriate values.
501 private boolean isMatchTo(String tagName
, XAttributeList list
) {
502 if (!name
.equals(tagName
)) return false;
503 boolean result
= true ;
504 for (int i
= 0; i
< attrList
.length
; i
++) {
505 result
&= checkAttr(i
, list
) ;
512 * Class realises extended XML data checking. It has possibilities
513 * to check if a tag exists, if it has some attributes with
514 * values, and if this tag is contained in another tag (which
515 * also can specify any attributes). It can check if some
516 * character data exists inside any tag specified.
518 public static class XMLChecker
extends XMLWellFormChecker
{
519 private final HashSet
<String
> tagSet
= new HashSet
<String
>() ;
520 private final ArrayList
<Tag
[]> tags
= new ArrayList
<Tag
[]>() ;
521 private final ArrayList
<Object
[]> chars
= new ArrayList
<Object
[]>() ;
522 private final ArrayList
<String
> tagStack
= new ArrayList
<String
>() ;
523 private final ArrayList
<AttributeList
> attrStack
= new ArrayList
<AttributeList
>() ;
525 public XMLChecker(PrintWriter log
, boolean writeXML
) {
526 super(log
, writeXML
) ;
529 public void addTag(Tag tag
) {
530 tags
.add(new Tag
[] {tag
, null}) ;
531 tagSet
.add(tag
.name
) ;
534 public void addTagEnclosed(Tag tag
, Tag outerTag
) {
535 tags
.add(new Tag
[] {tag
, outerTag
}) ;
536 tagSet
.add(tag
.name
) ;
541 public void addCharactersEnclosed(String ch
, Tag outerTag
) {
542 chars
.add(new Object
[] {ch
.trim(), outerTag
}) ;
546 public void startElement(String name
, XAttributeList attr
) {
548 super.startElement(name
, attr
);
550 if (tagSet
.contains(name
)) {
551 for (int i
= 0; i
< tags
.size(); i
++) {
552 Tag
[] tag
= tags
.get(i
);
553 if (tag
[0].isMatchTo(name
, attr
)) {
554 if (tag
[1] == null) {
557 boolean isInStack
= false ;
558 for (int j
= 0; j
< tagStack
.size(); j
++) {
559 if (tag
[1].isMatchTo(tagStack
.get(j
),
574 tagStack
.add(0, name
) ;
575 attrStack
.add(0, new AttributeList(attr
));
576 } catch (Exception e
) {
577 e
.printStackTrace(log
);
582 public void characters(String ch
) {
583 super.characters(ch
) ;
584 for (int i
= 0; i
< chars
.size(); i
++) {
585 Object
[] chr
= chars
.get(i
);
586 if (((String
) chr
[0]).equals(ch
)) {
587 if (chr
[1] == null) {
590 boolean isInStack
= false ;
591 for (int j
= 0; j
< tagStack
.size(); j
++) {
592 if (((Tag
) chr
[1]).isMatchTo(tagStack
.get(j
),
608 public void endElement(String name
) {
610 super.endElement(name
);
612 if (tagStack
.size() > 0) {
614 attrStack
.remove(0) ;
616 } catch(Exception e
) {
617 e
.printStackTrace(log
) ;
621 public boolean check() {
622 if (tags
.size()> 0) {
623 log
.println("!!! Error: Some tags were not found :") ;
624 for (int i
= 0; i
< tags
.size(); i
++) {
625 Tag
[] tag
= tags
.get(i
) ;
626 log
.println(" Tag " + tag
[0] + " was not found");
628 log
.println(" inside tag " + tag
[1]) ;
631 if (chars
.size() > 0) {
632 log
.println("!!! Error: Some character data blocks were not found :") ;
633 for (int i
= 0; i
< chars
.size(); i
++) {
634 Object
[] ch
= chars
.get(i
) ;
635 log
.println(" Character data \"" + ch
[0] + "\" was not found ") ;
637 log
.println(" inside tag " + ch
[1]) ;
642 log
.println("!!! Some errors were found in XML structure") ;
644 boolean result
= tags
.isEmpty() && chars
.isEmpty() && isWellFormed();
652 public static PropertyValue
[] createMediaDescriptor(String
[] propNames
, Object
[] values
) {
653 PropertyValue
[] props
= new PropertyValue
[propNames
.length
] ;
655 for (int i
= 0; i
< props
.length
; i
++) {
656 props
[i
] = new PropertyValue() ;
657 props
[i
].Name
= propNames
[i
] ;
658 if (values
!= null && i
< values
.length
) {
659 props
[i
].Value
= values
[i
] ;