1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_xmloff.hxx"
30 
31 #include "DomBuilderContext.hxx"
32 
33 #include <xmloff/nmspmap.hxx>
34 #include <xmloff/xmlimp.hxx>
35 #include "xmloff/xmlerror.hxx"
36 
37 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
38 #include <com/sun/star/uno/Reference.hxx>
39 #include <com/sun/star/uno/Sequence.hxx>
40 #include <com/sun/star/xml/dom/XAttr.hpp>
41 #include <com/sun/star/xml/dom/XDocumentBuilder.hpp>
42 #include <com/sun/star/xml/dom/XNode.hpp>
43 #include <com/sun/star/xml/dom/XElement.hpp>
44 #include <com/sun/star/xml/sax/XAttributeList.hpp>
45 #include <com/sun/star/xml/dom/NodeType.hpp>
46 
47 #include <rtl/ustring.hxx>
48 #include <tools/debug.hxx>
49 
50 #include <unotools/processfactory.hxx>
51 
52 
53 using com::sun::star::lang::XMultiServiceFactory;
54 using com::sun::star::uno::Reference;
55 using com::sun::star::uno::Sequence;
56 using com::sun::star::uno::UNO_QUERY;
57 using com::sun::star::uno::UNO_QUERY_THROW;
58 using com::sun::star::xml::dom::XAttr;
59 using com::sun::star::xml::dom::XDocument;
60 using com::sun::star::xml::dom::XDocumentBuilder;
61 using com::sun::star::xml::dom::XNode;
62 using com::sun::star::xml::dom::XElement;
63 using com::sun::star::xml::sax::XAttributeList;
64 using com::sun::star::xml::dom::NodeType_ELEMENT_NODE;
65 using rtl::OUString;
66 
67 
68 // helper functions; implemented below
69 Reference<XNode> lcl_createDomInstance();
70 Reference<XNode> lcl_createElement( SvXMLImport& rImport,
71                                     sal_uInt16 nPrefix,
72                                     const OUString rLocalName,
73                                     Reference<XNode> xParent);
74 
75 
76 DomBuilderContext::DomBuilderContext( SvXMLImport& rImport,
77                                       sal_uInt16 nPrefix,
78                                       const OUString& rLocalName ) :
79     SvXMLImportContext( rImport, nPrefix, rLocalName ),
80     mxNode( lcl_createElement( rImport, nPrefix, rLocalName,
81                                lcl_createDomInstance() ) )
82 {
83     DBG_ASSERT( mxNode.is(), "empty XNode not allowed" );
84     DBG_ASSERT( Reference<XElement>( mxNode, UNO_QUERY ).is(), "need element" );
85     DBG_ASSERT( mxNode->getNodeType() == NodeType_ELEMENT_NODE, "need element" );
86 }
87 
88 DomBuilderContext::DomBuilderContext( SvXMLImport& rImport,
89                                       sal_uInt16 nPrefix,
90                                       const OUString& rLocalName,
91                                       Reference<XNode>& xParent ) :
92     SvXMLImportContext( rImport, nPrefix, rLocalName ),
93     mxNode( lcl_createElement( rImport, nPrefix, rLocalName, xParent ) )
94 {
95     DBG_ASSERT( mxNode.is(), "empty XNode not allowed" );
96     DBG_ASSERT( Reference<XElement>( mxNode, UNO_QUERY ).is(), "need element" );
97     DBG_ASSERT( mxNode->getNodeType() == NodeType_ELEMENT_NODE, "need element" );
98 }
99 
100 DomBuilderContext::~DomBuilderContext()
101 {
102 }
103 
104 Reference<XDocument> DomBuilderContext::getTree()
105 {
106     DBG_ASSERT( mxNode.is(), "empty XNode not allowed" );
107     return mxNode->getOwnerDocument();
108 }
109 
110 Reference<XNode> DomBuilderContext::getNode()
111 {
112     return mxNode;
113 }
114 
115 
116 SvXMLImportContext* DomBuilderContext::CreateChildContext(
117     sal_uInt16 nPrefix,
118     const OUString& rLocalName,
119     const Reference<XAttributeList>& )
120 {
121     // create DomBuilder for subtree
122     return new DomBuilderContext( GetImport(), nPrefix, rLocalName, mxNode );
123 }
124 
125 
126 void DomBuilderContext::StartElement(
127     const Reference<XAttributeList>& xAttrList )
128 {
129     DBG_ASSERT( mxNode.is(), "empty XNode not allowed" );
130     DBG_ASSERT( mxNode->getOwnerDocument().is(), "XNode must have XDocument" );
131 
132     // add attribute nodes to new node
133     sal_Int16 nAttributeCount = xAttrList->getLength();
134     for( sal_Int16 i = 0; i < nAttributeCount; i++ )
135     {
136         // get name & value for attribute
137         const OUString& rName = xAttrList->getNameByIndex( i );
138         const OUString& rValue = xAttrList->getValueByIndex( i );
139 
140         // namespace handling: determine namespace & namespace keykey
141         OUString sNamespace;
142         sal_uInt16 nNamespaceKey =
143             GetImport().GetNamespaceMap()._GetKeyByAttrName(
144                 rName, NULL, NULL, &sNamespace );
145 
146         // create attribute node and set value
147         Reference<XElement> xElement( mxNode, UNO_QUERY_THROW );
148         switch( nNamespaceKey )
149         {
150         case XML_NAMESPACE_NONE:
151             // no namespace: create a non-namespaced attribute
152             xElement->setAttribute( rName, rValue );
153             break;
154         case XML_NAMESPACE_XMLNS:
155             // namespace declaration: ignore, since the DOM tree handles these
156             // declarations implicitly
157             break;
158         case XML_NAMESPACE_UNKNOWN:
159             // unknown namespace: illegal input. Raise Warning.
160             {
161                 Sequence<OUString> aSeq(2);
162                 aSeq[0] = rName;
163                 aSeq[1] = rValue;
164                 GetImport().SetError(
165                     XMLERROR_FLAG_WARNING | XMLERROR_NAMESPACE_TROUBLE, aSeq );
166             }
167             break;
168         default:
169             // a real and proper namespace: create namespaced attribute
170             xElement->setAttributeNS( sNamespace, rName, rValue );
171             break;
172         }
173     }
174 }
175 
176 void DomBuilderContext::EndElement()
177 {
178     // nothing to be done!
179 }
180 
181 void DomBuilderContext::Characters( const OUString& rCharacters )
182 {
183     DBG_ASSERT( mxNode.is(), "empty XNode not allowed" );
184 
185     // TODO: I assume adjacent text nodes should be joined, to preserve
186     // processinf model? (I.e., if the SAX parser breaks a string into 2
187     // Characters(..) calls, the DOM model would still see only one child.)
188 
189     // create text node and append to parent
190     Reference<XNode> xNew(
191         mxNode->getOwnerDocument()->createTextNode( rCharacters ),
192         UNO_QUERY_THROW );
193     mxNode->appendChild( xNew );
194 }
195 
196 
197 //
198 // helper function implementations
199 //
200 
201 const sal_Char sDocumentBuilder[] = "com.sun.star.xml.dom.DocumentBuilder";
202 
203 Reference<XNode> lcl_createDomInstance()
204 {
205     Reference<XMultiServiceFactory> xFactory = utl::getProcessServiceFactory();
206     DBG_ASSERT( xFactory.is(), "can't get service factory" );
207 
208     Reference<XDocumentBuilder> xBuilder(
209         xFactory->createInstance(
210             OUString( RTL_CONSTASCII_USTRINGPARAM( sDocumentBuilder ) ) ),
211         UNO_QUERY_THROW );
212 
213     return Reference<XNode>( xBuilder->newDocument(), UNO_QUERY_THROW );
214 }
215 
216 Reference<XNode> lcl_createElement( SvXMLImport& rImport,
217                                     sal_uInt16 nPrefix,
218                                     const OUString rLocalName,
219                                     Reference<XNode> xParent)
220 {
221     DBG_ASSERT( xParent.is(), "need parent node" );
222 
223     Reference<XDocument> xDocument = xParent->getOwnerDocument();
224     DBG_ASSERT( xDocument.is(), "no XDocument found!" );
225 
226     // TODO: come up with proper way of handling namespaces; re-creating the
227     // namespace from the key is NOT a good idea, and will not work for
228     // multiple prefixes for the same namespace. Fortunately, those are rare.
229 
230     Reference<XElement> xElement;
231     switch( nPrefix )
232     {
233     case XML_NAMESPACE_NONE:
234         // no namespace: use local name
235         xElement = xDocument->createElement( rLocalName );
236         break;
237     case XML_NAMESPACE_XMLNS:
238     case XML_NAMESPACE_UNKNOWN:
239         // both cases are illegal; raise warning (and use only local name)
240         xElement = xDocument->createElement( rLocalName );
241         {
242             Sequence<OUString> aSeq(1);
243             aSeq[0] = rLocalName;
244             rImport.SetError(
245                 XMLERROR_FLAG_WARNING | XMLERROR_NAMESPACE_TROUBLE, aSeq );
246         }
247         break;
248     default:
249         // We are only given the prefix and the local name; thus we have to ask
250         // the namespace map to create a qualified name for us. Technically,
251         // this is a bug, since this will fail for multiple prefixes used for
252         // the same namespace.
253         xElement = xDocument->createElementNS(
254             rImport.GetNamespaceMap().GetNameByKey( nPrefix ),
255             rImport.GetNamespaceMap().GetQNameByKey( nPrefix, rLocalName ) );
256         break;
257     }
258     DBG_ASSERT( xElement.is(), "can't create element" );
259 
260     // add new element to parent and return
261     Reference<XNode> xNode( xElement, UNO_QUERY_THROW );
262     xParent->appendChild( xNode );
263     return xNode;
264 }
265