1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is TransforMiiX XSLT processor code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2001
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Peter Van der Beken <peterv@propagandism.org>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "txMozillaXMLOutput.h"
40 :
41 : #include "nsIDocument.h"
42 : #include "nsIDocShell.h"
43 : #include "nsScriptLoader.h"
44 : #include "nsIDOMDocument.h"
45 : #include "nsIDOMDocumentType.h"
46 : #include "nsIScriptElement.h"
47 : #include "nsIParser.h"
48 : #include "nsIRefreshURI.h"
49 : #include "nsPIDOMWindow.h"
50 : #include "nsIContent.h"
51 : #include "nsContentCID.h"
52 : #include "nsNetUtil.h"
53 : #include "nsUnicharUtils.h"
54 : #include "nsGkAtoms.h"
55 : #include "txLog.h"
56 : #include "nsIConsoleService.h"
57 : #include "nsIDOMDocumentFragment.h"
58 : #include "nsINameSpaceManager.h"
59 : #include "nsCSSStyleSheet.h"
60 : #include "txStringUtils.h"
61 : #include "txURIUtils.h"
62 : #include "nsIHTMLDocument.h"
63 : #include "nsIStyleSheetLinkingElement.h"
64 : #include "nsIDocumentTransformer.h"
65 : #include "mozilla/css/Loader.h"
66 : #include "mozilla/dom/Element.h"
67 : #include "nsCharsetAlias.h"
68 : #include "nsIHTMLContentSink.h"
69 : #include "nsContentUtils.h"
70 : #include "txXMLUtils.h"
71 : #include "nsContentSink.h"
72 : #include "nsINode.h"
73 : #include "nsContentCreatorFunctions.h"
74 : #include "txError.h"
75 :
76 : using namespace mozilla::dom;
77 :
78 : #define TX_ENSURE_CURRENTNODE \
79 : NS_ASSERTION(mCurrentNode, "mCurrentNode is NULL"); \
80 : if (!mCurrentNode) \
81 : return NS_ERROR_UNEXPECTED
82 :
83 0 : txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat,
84 : nsITransformObserver* aObserver)
85 : : mTreeDepth(0),
86 : mBadChildLevel(0),
87 : mTableState(NORMAL),
88 : mCreatingNewDocument(true),
89 : mOpenedElementIsHTML(false),
90 : mRootContentCreated(false),
91 0 : mNoFixup(false)
92 : {
93 0 : MOZ_COUNT_CTOR(txMozillaXMLOutput);
94 0 : if (aObserver) {
95 0 : mNotifier = new txTransformNotifier();
96 0 : if (mNotifier) {
97 0 : mNotifier->Init(aObserver);
98 : }
99 : }
100 :
101 0 : mOutputFormat.merge(*aFormat);
102 0 : mOutputFormat.setFromDefaults();
103 0 : }
104 :
105 0 : txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat,
106 : nsIDOMDocumentFragment* aFragment,
107 : bool aNoFixup)
108 : : mTreeDepth(0),
109 : mBadChildLevel(0),
110 : mTableState(NORMAL),
111 : mCreatingNewDocument(false),
112 : mOpenedElementIsHTML(false),
113 : mRootContentCreated(false),
114 0 : mNoFixup(aNoFixup)
115 : {
116 0 : MOZ_COUNT_CTOR(txMozillaXMLOutput);
117 0 : mOutputFormat.merge(*aFormat);
118 0 : mOutputFormat.setFromDefaults();
119 :
120 0 : mCurrentNode = do_QueryInterface(aFragment);
121 0 : mDocument = mCurrentNode->OwnerDoc();
122 0 : mNodeInfoManager = mDocument->NodeInfoManager();
123 0 : }
124 :
125 0 : txMozillaXMLOutput::~txMozillaXMLOutput()
126 : {
127 0 : MOZ_COUNT_DTOR(txMozillaXMLOutput);
128 0 : }
129 :
130 : nsresult
131 0 : txMozillaXMLOutput::attribute(nsIAtom* aPrefix,
132 : nsIAtom* aLocalName,
133 : nsIAtom* aLowercaseLocalName,
134 : const PRInt32 aNsID,
135 : const nsString& aValue)
136 : {
137 0 : nsCOMPtr<nsIAtom> owner;
138 0 : if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) {
139 0 : if (aLowercaseLocalName) {
140 0 : aLocalName = aLowercaseLocalName;
141 : }
142 : else {
143 0 : owner = TX_ToLowerCaseAtom(aLocalName);
144 0 : NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY);
145 :
146 0 : aLocalName = owner;
147 : }
148 : }
149 :
150 0 : return attributeInternal(aPrefix, aLocalName, aNsID, aValue);
151 : }
152 :
153 : nsresult
154 0 : txMozillaXMLOutput::attribute(nsIAtom* aPrefix,
155 : const nsSubstring& aLocalName,
156 : const PRInt32 aNsID,
157 : const nsString& aValue)
158 : {
159 0 : nsCOMPtr<nsIAtom> lname;
160 :
161 0 : if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) {
162 0 : nsAutoString lnameStr;
163 0 : nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
164 0 : lname = do_GetAtom(lnameStr);
165 : }
166 : else {
167 0 : lname = do_GetAtom(aLocalName);
168 : }
169 :
170 0 : NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY);
171 :
172 : // Check that it's a valid name
173 0 : if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) {
174 : // Try without prefix
175 0 : aPrefix = nsnull;
176 0 : if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) {
177 : // Don't return error here since the callers don't deal
178 0 : return NS_OK;
179 : }
180 : }
181 :
182 0 : return attributeInternal(aPrefix, lname, aNsID, aValue);
183 : }
184 :
185 : nsresult
186 0 : txMozillaXMLOutput::attributeInternal(nsIAtom* aPrefix,
187 : nsIAtom* aLocalName,
188 : PRInt32 aNsID,
189 : const nsString& aValue)
190 : {
191 0 : if (!mOpenedElement) {
192 : // XXX Signal this? (can't add attributes after element closed)
193 0 : return NS_OK;
194 : }
195 :
196 0 : NS_ASSERTION(!mBadChildLevel, "mBadChildLevel set when element is opened");
197 :
198 0 : return mOpenedElement->SetAttr(aNsID, aLocalName, aPrefix, aValue,
199 0 : false);
200 : }
201 :
202 : nsresult
203 0 : txMozillaXMLOutput::characters(const nsSubstring& aData, bool aDOE)
204 : {
205 0 : nsresult rv = closePrevious(false);
206 0 : NS_ENSURE_SUCCESS(rv, rv);
207 :
208 0 : if (!mBadChildLevel) {
209 0 : mText.Append(aData);
210 : }
211 :
212 0 : return NS_OK;
213 : }
214 :
215 : nsresult
216 0 : txMozillaXMLOutput::comment(const nsString& aData)
217 : {
218 0 : nsresult rv = closePrevious(true);
219 0 : NS_ENSURE_SUCCESS(rv, rv);
220 :
221 0 : if (mBadChildLevel) {
222 0 : return NS_OK;
223 : }
224 :
225 0 : TX_ENSURE_CURRENTNODE;
226 :
227 0 : nsCOMPtr<nsIContent> comment;
228 0 : rv = NS_NewCommentNode(getter_AddRefs(comment), mNodeInfoManager);
229 0 : NS_ENSURE_SUCCESS(rv, rv);
230 :
231 0 : rv = comment->SetText(aData, false);
232 0 : NS_ENSURE_SUCCESS(rv, rv);
233 :
234 0 : return mCurrentNode->AppendChildTo(comment, true);
235 : }
236 :
237 : nsresult
238 0 : txMozillaXMLOutput::endDocument(nsresult aResult)
239 : {
240 0 : TX_ENSURE_CURRENTNODE;
241 :
242 0 : if (NS_FAILED(aResult)) {
243 0 : if (mNotifier) {
244 0 : mNotifier->OnTransformEnd(aResult);
245 : }
246 :
247 0 : return NS_OK;
248 : }
249 :
250 0 : nsresult rv = closePrevious(true);
251 0 : if (NS_FAILED(rv)) {
252 0 : if (mNotifier) {
253 0 : mNotifier->OnTransformEnd(rv);
254 : }
255 :
256 0 : return rv;
257 : }
258 :
259 0 : if (mCreatingNewDocument) {
260 : // This should really be handled by nsIDocument::EndLoad
261 0 : mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE);
262 0 : nsScriptLoader* loader = mDocument->ScriptLoader();
263 0 : if (loader) {
264 0 : loader->ParsingComplete(false);
265 : }
266 : }
267 :
268 0 : if (!mRefreshString.IsEmpty()) {
269 0 : nsPIDOMWindow *win = mDocument->GetWindow();
270 0 : if (win) {
271 : nsCOMPtr<nsIRefreshURI> refURI =
272 0 : do_QueryInterface(win->GetDocShell());
273 0 : if (refURI) {
274 0 : refURI->SetupRefreshURIFromHeader(mDocument->GetDocBaseURI(),
275 0 : mRefreshString);
276 : }
277 : }
278 : }
279 :
280 0 : if (mNotifier) {
281 0 : mNotifier->OnTransformEnd();
282 : }
283 :
284 0 : return NS_OK;
285 : }
286 :
287 : nsresult
288 0 : txMozillaXMLOutput::endElement()
289 : {
290 0 : TX_ENSURE_CURRENTNODE;
291 :
292 0 : if (mBadChildLevel) {
293 0 : --mBadChildLevel;
294 0 : PR_LOG(txLog::xslt, PR_LOG_DEBUG,
295 : ("endElement, mBadChildLevel = %d\n", mBadChildLevel));
296 0 : return NS_OK;
297 : }
298 :
299 0 : --mTreeDepth;
300 :
301 0 : nsresult rv = closePrevious(true);
302 0 : NS_ENSURE_SUCCESS(rv, rv);
303 :
304 0 : NS_ASSERTION(mCurrentNode->IsElement(), "borked mCurrentNode");
305 0 : NS_ENSURE_TRUE(mCurrentNode->IsElement(), NS_ERROR_UNEXPECTED);
306 :
307 0 : Element* element = mCurrentNode->AsElement();
308 :
309 : // Handle html-elements
310 0 : if (!mNoFixup) {
311 0 : if (element->IsHTML()) {
312 0 : rv = endHTMLElement(element);
313 0 : NS_ENSURE_SUCCESS(rv, rv);
314 : }
315 :
316 : // Handle elements that are different when parser-created
317 0 : PRInt32 ns = element->GetNameSpaceID();
318 0 : nsIAtom* localName = element->Tag();
319 :
320 0 : if ((ns == kNameSpaceID_XHTML && (localName == nsGkAtoms::title ||
321 : localName == nsGkAtoms::object ||
322 : localName == nsGkAtoms::applet ||
323 : localName == nsGkAtoms::select ||
324 : localName == nsGkAtoms::textarea)) ||
325 : (ns == kNameSpaceID_SVG && localName == nsGkAtoms::title)) {
326 0 : element->DoneAddingChildren(true);
327 0 : } else if ((ns == kNameSpaceID_XHTML || ns == kNameSpaceID_SVG) &&
328 : localName == nsGkAtoms::script) {
329 0 : nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element);
330 0 : NS_ABORT_IF_FALSE(sele, "script elements need to implement nsIScriptElement");
331 0 : bool block = sele->AttemptToExecute();
332 : // If the act of insertion evaluated the script, we're fine.
333 : // Else, add this script element to the array of loading scripts.
334 0 : if (block) {
335 0 : rv = mNotifier->AddScriptElement(sele);
336 0 : NS_ENSURE_SUCCESS(rv, rv);
337 0 : }
338 0 : } else if (ns == kNameSpaceID_XHTML &&
339 : (localName == nsGkAtoms::input ||
340 : localName == nsGkAtoms::button ||
341 : localName == nsGkAtoms::menuitem
342 : #ifdef MOZ_MEDIA
343 : ||
344 : localName == nsGkAtoms::audio ||
345 : localName == nsGkAtoms::video
346 : #endif
347 : )) {
348 0 : element->DoneCreatingElement();
349 : }
350 : }
351 :
352 0 : if (mCreatingNewDocument) {
353 : // Handle all sorts of stylesheets
354 : nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
355 0 : do_QueryInterface(mCurrentNode);
356 0 : if (ssle) {
357 0 : ssle->SetEnableUpdates(true);
358 : bool willNotify;
359 : bool isAlternate;
360 0 : nsresult rv = ssle->UpdateStyleSheet(mNotifier, &willNotify,
361 0 : &isAlternate);
362 0 : if (mNotifier && NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
363 0 : mNotifier->AddPendingStylesheet();
364 : }
365 : }
366 : }
367 :
368 : // Add the element to the tree if it wasn't added before and take one step
369 : // up the tree
370 0 : PRUint32 last = mCurrentNodeStack.Count() - 1;
371 0 : NS_ASSERTION(last != (PRUint32)-1, "empty stack");
372 :
373 0 : nsCOMPtr<nsINode> parent = mCurrentNodeStack.SafeObjectAt(last);
374 0 : mCurrentNodeStack.RemoveObjectAt(last);
375 :
376 0 : if (mCurrentNode == mNonAddedNode) {
377 0 : if (parent == mDocument) {
378 0 : NS_ASSERTION(!mRootContentCreated,
379 : "Parent to add to shouldn't be a document if we "
380 : "have a root content");
381 0 : mRootContentCreated = true;
382 : }
383 :
384 : // Check to make sure that script hasn't inserted the node somewhere
385 : // else in the tree
386 0 : if (!mCurrentNode->GetNodeParent()) {
387 0 : parent->AppendChildTo(mNonAddedNode, true);
388 : }
389 0 : mNonAddedNode = nsnull;
390 : }
391 :
392 0 : mCurrentNode = parent;
393 :
394 : mTableState =
395 0 : static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop()));
396 :
397 0 : return NS_OK;
398 : }
399 :
400 0 : void txMozillaXMLOutput::getOutputDocument(nsIDOMDocument** aDocument)
401 : {
402 0 : CallQueryInterface(mDocument, aDocument);
403 0 : }
404 :
405 : nsresult
406 0 : txMozillaXMLOutput::processingInstruction(const nsString& aTarget, const nsString& aData)
407 : {
408 0 : nsresult rv = closePrevious(true);
409 0 : NS_ENSURE_SUCCESS(rv, rv);
410 :
411 0 : if (mOutputFormat.mMethod == eHTMLOutput)
412 0 : return NS_OK;
413 :
414 0 : TX_ENSURE_CURRENTNODE;
415 :
416 0 : rv = nsContentUtils::CheckQName(aTarget, false);
417 0 : NS_ENSURE_SUCCESS(rv, rv);
418 :
419 0 : nsCOMPtr<nsIContent> pi;
420 0 : rv = NS_NewXMLProcessingInstruction(getter_AddRefs(pi),
421 0 : mNodeInfoManager, aTarget, aData);
422 0 : NS_ENSURE_SUCCESS(rv, rv);
423 :
424 0 : nsCOMPtr<nsIStyleSheetLinkingElement> ssle;
425 0 : if (mCreatingNewDocument) {
426 0 : ssle = do_QueryInterface(pi);
427 0 : if (ssle) {
428 0 : ssle->InitStyleLinkElement(false);
429 0 : ssle->SetEnableUpdates(false);
430 : }
431 : }
432 :
433 0 : rv = mCurrentNode->AppendChildTo(pi, true);
434 0 : NS_ENSURE_SUCCESS(rv, rv);
435 :
436 0 : if (ssle) {
437 0 : ssle->SetEnableUpdates(true);
438 : bool willNotify;
439 : bool isAlternate;
440 0 : rv = ssle->UpdateStyleSheet(mNotifier, &willNotify, &isAlternate);
441 0 : if (mNotifier && NS_SUCCEEDED(rv) && willNotify && !isAlternate) {
442 0 : mNotifier->AddPendingStylesheet();
443 : }
444 : }
445 :
446 0 : return NS_OK;
447 : }
448 :
449 : nsresult
450 0 : txMozillaXMLOutput::startDocument()
451 : {
452 0 : if (mNotifier) {
453 0 : mNotifier->OnTransformStart();
454 : }
455 :
456 0 : if (mCreatingNewDocument) {
457 0 : nsScriptLoader* loader = mDocument->ScriptLoader();
458 0 : if (loader) {
459 0 : loader->BeginDeferringScripts();
460 : }
461 : }
462 :
463 0 : return NS_OK;
464 : }
465 :
466 : nsresult
467 0 : txMozillaXMLOutput::startElement(nsIAtom* aPrefix, nsIAtom* aLocalName,
468 : nsIAtom* aLowercaseLocalName,
469 : const PRInt32 aNsID)
470 : {
471 0 : NS_PRECONDITION(aNsID != kNameSpaceID_None || !aPrefix,
472 : "Can't have prefix without namespace");
473 :
474 0 : if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
475 0 : nsCOMPtr<nsIAtom> owner;
476 0 : if (!aLowercaseLocalName) {
477 0 : owner = TX_ToLowerCaseAtom(aLocalName);
478 0 : NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY);
479 :
480 0 : aLowercaseLocalName = owner;
481 : }
482 : return startElementInternal(nsnull,
483 : aLowercaseLocalName,
484 0 : kNameSpaceID_XHTML);
485 : }
486 :
487 0 : return startElementInternal(aPrefix, aLocalName, aNsID);
488 : }
489 :
490 : nsresult
491 0 : txMozillaXMLOutput::startElement(nsIAtom* aPrefix,
492 : const nsSubstring& aLocalName,
493 : const PRInt32 aNsID)
494 : {
495 0 : PRInt32 nsId = aNsID;
496 0 : nsCOMPtr<nsIAtom> lname;
497 :
498 0 : if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) {
499 0 : nsId = kNameSpaceID_XHTML;
500 :
501 0 : nsAutoString lnameStr;
502 0 : nsContentUtils::ASCIIToLower(aLocalName, lnameStr);
503 0 : lname = do_GetAtom(lnameStr);
504 : }
505 : else {
506 0 : lname = do_GetAtom(aLocalName);
507 : }
508 :
509 : // No biggie if we lose the prefix due to OOM
510 0 : NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY);
511 :
512 : // Check that it's a valid name
513 0 : if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) {
514 : // Try without prefix
515 0 : aPrefix = nsnull;
516 0 : if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) {
517 0 : return NS_ERROR_XSLT_BAD_NODE_NAME;
518 : }
519 : }
520 :
521 0 : return startElementInternal(aPrefix, lname, nsId);
522 : }
523 :
524 : nsresult
525 0 : txMozillaXMLOutput::startElementInternal(nsIAtom* aPrefix,
526 : nsIAtom* aLocalName,
527 : PRInt32 aNsID)
528 : {
529 0 : TX_ENSURE_CURRENTNODE;
530 :
531 0 : if (mBadChildLevel) {
532 0 : ++mBadChildLevel;
533 0 : PR_LOG(txLog::xslt, PR_LOG_DEBUG,
534 : ("startElement, mBadChildLevel = %d\n", mBadChildLevel));
535 0 : return NS_OK;
536 : }
537 :
538 0 : nsresult rv = closePrevious(true);
539 0 : NS_ENSURE_SUCCESS(rv, rv);
540 :
541 : // Push and init state
542 0 : if (mTreeDepth == MAX_REFLOW_DEPTH) {
543 : // eCloseElement couldn't add the parent so we fail as well or we've
544 : // reached the limit of the depth of the tree that we allow.
545 0 : ++mBadChildLevel;
546 0 : PR_LOG(txLog::xslt, PR_LOG_DEBUG,
547 : ("startElement, mBadChildLevel = %d\n", mBadChildLevel));
548 0 : return NS_OK;
549 : }
550 :
551 0 : ++mTreeDepth;
552 :
553 0 : rv = mTableStateStack.push(NS_INT32_TO_PTR(mTableState));
554 0 : NS_ENSURE_SUCCESS(rv, rv);
555 :
556 0 : if (!mCurrentNodeStack.AppendObject(mCurrentNode)) {
557 0 : return NS_ERROR_OUT_OF_MEMORY;
558 : }
559 :
560 0 : mTableState = NORMAL;
561 0 : mOpenedElementIsHTML = false;
562 :
563 : // Create the element
564 : nsCOMPtr<nsINodeInfo> ni =
565 : mNodeInfoManager->GetNodeInfo(aLocalName, aPrefix, aNsID,
566 0 : nsIDOMNode::ELEMENT_NODE);
567 0 : NS_ENSURE_TRUE(ni, NS_ERROR_OUT_OF_MEMORY);
568 :
569 0 : NS_NewElement(getter_AddRefs(mOpenedElement), ni.forget(),
570 : mCreatingNewDocument ?
571 0 : FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT);
572 :
573 : // Set up the element and adjust state
574 0 : if (!mNoFixup) {
575 0 : if (aNsID == kNameSpaceID_XHTML) {
576 0 : mOpenedElementIsHTML = (mOutputFormat.mMethod == eHTMLOutput);
577 0 : rv = startHTMLElement(mOpenedElement, mOpenedElementIsHTML);
578 0 : NS_ENSURE_SUCCESS(rv, rv);
579 :
580 : }
581 : }
582 :
583 0 : if (mCreatingNewDocument) {
584 : // Handle all sorts of stylesheets
585 : nsCOMPtr<nsIStyleSheetLinkingElement> ssle =
586 0 : do_QueryInterface(mOpenedElement);
587 0 : if (ssle) {
588 0 : ssle->InitStyleLinkElement(false);
589 0 : ssle->SetEnableUpdates(false);
590 : }
591 : }
592 :
593 0 : return NS_OK;
594 : }
595 :
596 : nsresult
597 0 : txMozillaXMLOutput::closePrevious(bool aFlushText)
598 : {
599 0 : TX_ENSURE_CURRENTNODE;
600 :
601 : nsresult rv;
602 0 : if (mOpenedElement) {
603 0 : bool currentIsDoc = mCurrentNode == mDocument;
604 0 : if (currentIsDoc && mRootContentCreated) {
605 : // We already have a document element, but the XSLT spec allows this.
606 : // As a workaround, create a wrapper object and use that as the
607 : // document element.
608 :
609 0 : rv = createTxWrapper();
610 0 : NS_ENSURE_SUCCESS(rv, rv);
611 : }
612 :
613 0 : rv = mCurrentNode->AppendChildTo(mOpenedElement, true);
614 0 : NS_ENSURE_SUCCESS(rv, rv);
615 :
616 0 : if (currentIsDoc) {
617 0 : mRootContentCreated = true;
618 0 : nsContentSink::NotifyDocElementCreated(mDocument);
619 : }
620 :
621 0 : mCurrentNode = mOpenedElement;
622 0 : mOpenedElement = nsnull;
623 : }
624 0 : else if (aFlushText && !mText.IsEmpty()) {
625 : // Text can't appear in the root of a document
626 0 : if (mDocument == mCurrentNode) {
627 0 : if (XMLUtils::isWhitespace(mText)) {
628 0 : mText.Truncate();
629 :
630 0 : return NS_OK;
631 : }
632 :
633 0 : rv = createTxWrapper();
634 0 : NS_ENSURE_SUCCESS(rv, rv);
635 : }
636 0 : nsCOMPtr<nsIContent> text;
637 0 : rv = NS_NewTextNode(getter_AddRefs(text), mNodeInfoManager);
638 0 : NS_ENSURE_SUCCESS(rv, rv);
639 :
640 0 : rv = text->SetText(mText, false);
641 0 : NS_ENSURE_SUCCESS(rv, rv);
642 :
643 0 : rv = mCurrentNode->AppendChildTo(text, true);
644 0 : NS_ENSURE_SUCCESS(rv, rv);
645 :
646 0 : mText.Truncate();
647 : }
648 :
649 0 : return NS_OK;
650 : }
651 :
652 : nsresult
653 0 : txMozillaXMLOutput::createTxWrapper()
654 : {
655 0 : NS_ASSERTION(mDocument == mCurrentNode,
656 : "creating wrapper when document isn't parent");
657 :
658 : PRInt32 namespaceID;
659 0 : nsresult rv = nsContentUtils::NameSpaceManager()->
660 0 : RegisterNameSpace(NS_LITERAL_STRING(kTXNameSpaceURI), namespaceID);
661 0 : NS_ENSURE_SUCCESS(rv, rv);
662 :
663 0 : nsCOMPtr<nsIContent> wrapper;
664 0 : rv = mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result),
665 : nsGkAtoms::transformiix, namespaceID,
666 0 : getter_AddRefs(wrapper));
667 0 : NS_ENSURE_SUCCESS(rv, rv);
668 :
669 0 : PRUint32 i, j, childCount = mDocument->GetChildCount();
670 : #ifdef DEBUG
671 : // Keep track of the location of the current documentElement, if there is
672 : // one, so we can verify later
673 0 : PRUint32 rootLocation = 0;
674 : #endif
675 0 : for (i = 0, j = 0; i < childCount; ++i) {
676 0 : nsCOMPtr<nsIContent> childContent = mDocument->GetChildAt(j);
677 :
678 : #ifdef DEBUG
679 0 : if (childContent->IsElement()) {
680 0 : rootLocation = j;
681 : }
682 : #endif
683 :
684 0 : if (childContent->Tag() == nsGkAtoms::documentTypeNodeName) {
685 : #ifdef DEBUG
686 : // The new documentElement should go after the document type.
687 : // This is needed for cases when there is no existing
688 : // documentElement in the document.
689 0 : rootLocation = NS_MAX(rootLocation, j + 1);
690 : #endif
691 0 : ++j;
692 : }
693 : else {
694 0 : rv = mDocument->RemoveChildAt(j, true);
695 0 : NS_ENSURE_SUCCESS(rv, rv);
696 :
697 0 : rv = wrapper->AppendChildTo(childContent, true);
698 0 : NS_ENSURE_SUCCESS(rv, rv);
699 : break;
700 : }
701 : }
702 :
703 0 : if (!mCurrentNodeStack.AppendObject(wrapper)) {
704 0 : return NS_ERROR_OUT_OF_MEMORY;
705 : }
706 0 : mCurrentNode = wrapper;
707 0 : mRootContentCreated = true;
708 0 : NS_ASSERTION(rootLocation == mDocument->GetChildCount(),
709 : "Incorrect root location");
710 0 : return mDocument->AppendChildTo(wrapper, true);
711 : }
712 :
713 : nsresult
714 0 : txMozillaXMLOutput::startHTMLElement(nsIContent* aElement, bool aIsHTML)
715 : {
716 0 : nsresult rv = NS_OK;
717 0 : nsIAtom *atom = aElement->Tag();
718 :
719 0 : if ((atom != nsGkAtoms::tr || !aIsHTML) &&
720 0 : NS_PTR_TO_INT32(mTableStateStack.peek()) == ADDED_TBODY) {
721 0 : PRUint32 last = mCurrentNodeStack.Count() - 1;
722 0 : NS_ASSERTION(last != (PRUint32)-1, "empty stack");
723 :
724 0 : mCurrentNode = mCurrentNodeStack.SafeObjectAt(last);
725 0 : mCurrentNodeStack.RemoveObjectAt(last);
726 0 : mTableStateStack.pop();
727 : }
728 :
729 0 : if (atom == nsGkAtoms::table && aIsHTML) {
730 0 : mTableState = TABLE;
731 : }
732 0 : else if (atom == nsGkAtoms::tr && aIsHTML &&
733 0 : NS_PTR_TO_INT32(mTableStateStack.peek()) == TABLE) {
734 0 : nsCOMPtr<nsIContent> tbody;
735 0 : rv = createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody));
736 0 : NS_ENSURE_SUCCESS(rv, rv);
737 :
738 0 : rv = mCurrentNode->AppendChildTo(tbody, true);
739 0 : NS_ENSURE_SUCCESS(rv, rv);
740 :
741 0 : rv = mTableStateStack.push(NS_INT32_TO_PTR(ADDED_TBODY));
742 0 : NS_ENSURE_SUCCESS(rv, rv);
743 :
744 0 : if (!mCurrentNodeStack.AppendObject(tbody)) {
745 0 : return NS_ERROR_OUT_OF_MEMORY;
746 : }
747 :
748 0 : mCurrentNode = tbody;
749 : }
750 0 : else if (atom == nsGkAtoms::head &&
751 : mOutputFormat.mMethod == eHTMLOutput) {
752 : // Insert META tag, according to spec, 16.2, like
753 : // <META http-equiv="Content-Type" content="text/html; charset=EUC-JP">
754 0 : nsCOMPtr<nsIContent> meta;
755 0 : rv = createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta));
756 0 : NS_ENSURE_SUCCESS(rv, rv);
757 :
758 : rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
759 0 : NS_LITERAL_STRING("Content-Type"), false);
760 0 : NS_ENSURE_SUCCESS(rv, rv);
761 :
762 0 : nsAutoString metacontent;
763 0 : metacontent.Append(mOutputFormat.mMediaType);
764 0 : metacontent.AppendLiteral("; charset=");
765 0 : metacontent.Append(mOutputFormat.mEncoding);
766 : rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::content,
767 0 : metacontent, false);
768 0 : NS_ENSURE_SUCCESS(rv, rv);
769 :
770 : // No need to notify since aElement hasn't been inserted yet
771 0 : NS_ASSERTION(!aElement->IsInDoc(), "should not be in doc");
772 0 : rv = aElement->AppendChildTo(meta, false);
773 0 : NS_ENSURE_SUCCESS(rv, rv);
774 : }
775 :
776 0 : return NS_OK;
777 : }
778 :
779 : nsresult
780 0 : txMozillaXMLOutput::endHTMLElement(nsIContent* aElement)
781 : {
782 0 : nsIAtom *atom = aElement->Tag();
783 :
784 0 : if (mTableState == ADDED_TBODY) {
785 0 : NS_ASSERTION(atom == nsGkAtoms::tbody,
786 : "Element flagged as added tbody isn't a tbody");
787 0 : PRUint32 last = mCurrentNodeStack.Count() - 1;
788 0 : NS_ASSERTION(last != (PRUint32)-1, "empty stack");
789 :
790 0 : mCurrentNode = mCurrentNodeStack.SafeObjectAt(last);
791 0 : mCurrentNodeStack.RemoveObjectAt(last);
792 : mTableState = static_cast<TableState>
793 0 : (NS_PTR_TO_INT32(mTableStateStack.pop()));
794 :
795 0 : return NS_OK;
796 : }
797 0 : else if (mCreatingNewDocument && atom == nsGkAtoms::meta) {
798 : // handle HTTP-EQUIV data
799 0 : nsAutoString httpEquiv;
800 0 : aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, httpEquiv);
801 0 : if (!httpEquiv.IsEmpty()) {
802 0 : nsAutoString value;
803 0 : aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, value);
804 0 : if (!value.IsEmpty()) {
805 0 : nsContentUtils::ASCIIToLower(httpEquiv);
806 0 : nsCOMPtr<nsIAtom> header = do_GetAtom(httpEquiv);
807 0 : processHTTPEquiv(header, value);
808 : }
809 : }
810 : }
811 :
812 0 : return NS_OK;
813 : }
814 :
815 0 : void txMozillaXMLOutput::processHTTPEquiv(nsIAtom* aHeader, const nsString& aValue)
816 : {
817 : // For now we only handle "refresh". There's a longer list in
818 : // HTMLContentSink::ProcessHeaderData
819 0 : if (aHeader == nsGkAtoms::refresh)
820 0 : LossyCopyUTF16toASCII(aValue, mRefreshString);
821 0 : }
822 :
823 : nsresult
824 0 : txMozillaXMLOutput::createResultDocument(const nsSubstring& aName, PRInt32 aNsID,
825 : nsIDOMDocument* aSourceDocument)
826 : {
827 : nsresult rv;
828 :
829 : // Create the document
830 0 : if (mOutputFormat.mMethod == eHTMLOutput) {
831 0 : rv = NS_NewHTMLDocument(getter_AddRefs(mDocument));
832 0 : NS_ENSURE_SUCCESS(rv, rv);
833 : }
834 : else {
835 : // We should check the root name/namespace here and create the
836 : // appropriate document
837 0 : rv = NS_NewXMLDocument(getter_AddRefs(mDocument));
838 0 : NS_ENSURE_SUCCESS(rv, rv);
839 : }
840 : // This should really be handled by nsIDocument::BeginLoad
841 0 : mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_LOADING);
842 0 : nsCOMPtr<nsIDocument> source = do_QueryInterface(aSourceDocument);
843 0 : NS_ENSURE_STATE(source);
844 0 : bool hasHadScriptObject = false;
845 : nsIScriptGlobalObject* sgo =
846 0 : source->GetScriptHandlingObject(hasHadScriptObject);
847 0 : NS_ENSURE_STATE(sgo || !hasHadScriptObject);
848 0 : mDocument->SetScriptHandlingObject(sgo);
849 :
850 0 : mCurrentNode = mDocument;
851 0 : mNodeInfoManager = mDocument->NodeInfoManager();
852 :
853 : // Reset and set up the document
854 0 : URIUtils::ResetWithSource(mDocument, aSourceDocument);
855 :
856 : // Set the charset
857 0 : if (!mOutputFormat.mEncoding.IsEmpty()) {
858 0 : NS_LossyConvertUTF16toASCII charset(mOutputFormat.mEncoding);
859 0 : nsCAutoString canonicalCharset;
860 0 : if (NS_SUCCEEDED(nsCharsetAlias::GetPreferred(charset, canonicalCharset))) {
861 0 : mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent);
862 0 : mDocument->SetDocumentCharacterSet(canonicalCharset);
863 : }
864 : }
865 :
866 : // Set the mime-type
867 0 : if (!mOutputFormat.mMediaType.IsEmpty()) {
868 0 : mDocument->SetContentType(mOutputFormat.mMediaType);
869 : }
870 0 : else if (mOutputFormat.mMethod == eHTMLOutput) {
871 0 : mDocument->SetContentType(NS_LITERAL_STRING("text/html"));
872 : }
873 : else {
874 0 : mDocument->SetContentType(NS_LITERAL_STRING("application/xml"));
875 : }
876 :
877 0 : if (mOutputFormat.mMethod == eXMLOutput &&
878 : mOutputFormat.mOmitXMLDeclaration != eTrue) {
879 : PRInt32 standalone;
880 0 : if (mOutputFormat.mStandalone == eNotSet) {
881 0 : standalone = -1;
882 : }
883 0 : else if (mOutputFormat.mStandalone == eFalse) {
884 0 : standalone = 0;
885 : }
886 : else {
887 0 : standalone = 1;
888 : }
889 :
890 : // Could use mOutputFormat.mVersion.get() when we support
891 : // versions > 1.0.
892 : static const PRUnichar kOneDotZero[] = { '1', '.', '0', '\0' };
893 0 : mDocument->SetXMLDeclaration(kOneDotZero, mOutputFormat.mEncoding.get(),
894 0 : standalone);
895 : }
896 :
897 : // Set up script loader of the result document.
898 0 : nsScriptLoader *loader = mDocument->ScriptLoader();
899 0 : if (mNotifier) {
900 0 : loader->AddObserver(mNotifier);
901 : }
902 : else {
903 : // Don't load scripts, we can't notify the caller when they're loaded.
904 0 : loader->SetEnabled(false);
905 : }
906 :
907 0 : if (mNotifier) {
908 0 : rv = mNotifier->SetOutputDocument(mDocument);
909 0 : NS_ENSURE_SUCCESS(rv, rv);
910 : }
911 :
912 : // Do this after calling OnDocumentCreated to ensure that the
913 : // PresShell/PresContext has been hooked up and get notified.
914 0 : nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
915 0 : if (htmlDoc) {
916 0 : htmlDoc->SetCompatibilityMode(eCompatibility_FullStandards);
917 : }
918 :
919 : // Add a doc-type if requested
920 0 : if (!mOutputFormat.mSystemId.IsEmpty()) {
921 0 : nsAutoString qName;
922 0 : if (mOutputFormat.mMethod == eHTMLOutput) {
923 0 : qName.AssignLiteral("html");
924 : }
925 : else {
926 0 : qName.Assign(aName);
927 : }
928 :
929 0 : nsCOMPtr<nsIDOMDocumentType> documentType;
930 :
931 0 : nsresult rv = nsContentUtils::CheckQName(qName);
932 0 : if (NS_SUCCEEDED(rv)) {
933 0 : nsCOMPtr<nsIAtom> doctypeName = do_GetAtom(qName);
934 0 : if (!doctypeName) {
935 0 : return NS_ERROR_OUT_OF_MEMORY;
936 : }
937 :
938 : // Indicate that there is no internal subset (not just an empty one)
939 0 : rv = NS_NewDOMDocumentType(getter_AddRefs(documentType),
940 : mNodeInfoManager,
941 : doctypeName,
942 : mOutputFormat.mPublicId,
943 : mOutputFormat.mSystemId,
944 0 : NullString());
945 0 : NS_ENSURE_SUCCESS(rv, rv);
946 :
947 0 : nsCOMPtr<nsIContent> docType = do_QueryInterface(documentType);
948 0 : rv = mDocument->AppendChildTo(docType, true);
949 0 : NS_ENSURE_SUCCESS(rv, rv);
950 : }
951 : }
952 :
953 0 : return NS_OK;
954 : }
955 :
956 : nsresult
957 0 : txMozillaXMLOutput::createHTMLElement(nsIAtom* aName,
958 : nsIContent** aResult)
959 : {
960 0 : NS_ASSERTION(mOutputFormat.mMethod == eHTMLOutput,
961 : "need to adjust createHTMLElement");
962 :
963 0 : *aResult = nsnull;
964 :
965 0 : nsCOMPtr<nsINodeInfo> ni;
966 : ni = mNodeInfoManager->GetNodeInfo(aName, nsnull,
967 : kNameSpaceID_XHTML,
968 0 : nsIDOMNode::ELEMENT_NODE);
969 0 : NS_ENSURE_TRUE(ni, NS_ERROR_OUT_OF_MEMORY);
970 :
971 : return NS_NewHTMLElement(aResult, ni.forget(), mCreatingNewDocument ?
972 0 : FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT);
973 :
974 : }
975 :
976 0 : txTransformNotifier::txTransformNotifier()
977 : : mPendingStylesheetCount(0),
978 0 : mInTransform(false)
979 : {
980 0 : }
981 :
982 0 : NS_IMPL_ISUPPORTS2(txTransformNotifier,
983 : nsIScriptLoaderObserver,
984 : nsICSSLoaderObserver)
985 :
986 : NS_IMETHODIMP
987 0 : txTransformNotifier::ScriptAvailable(nsresult aResult,
988 : nsIScriptElement *aElement,
989 : bool aIsInline,
990 : nsIURI *aURI,
991 : PRInt32 aLineNo)
992 : {
993 0 : if (NS_FAILED(aResult) &&
994 0 : mScriptElements.RemoveObject(aElement)) {
995 0 : SignalTransformEnd();
996 : }
997 :
998 0 : return NS_OK;
999 : }
1000 :
1001 : NS_IMETHODIMP
1002 0 : txTransformNotifier::ScriptEvaluated(nsresult aResult,
1003 : nsIScriptElement *aElement,
1004 : bool aIsInline)
1005 : {
1006 0 : if (mScriptElements.RemoveObject(aElement)) {
1007 0 : SignalTransformEnd();
1008 : }
1009 :
1010 0 : return NS_OK;
1011 : }
1012 :
1013 : NS_IMETHODIMP
1014 0 : txTransformNotifier::StyleSheetLoaded(nsCSSStyleSheet* aSheet,
1015 : bool aWasAlternate,
1016 : nsresult aStatus)
1017 : {
1018 0 : if (mPendingStylesheetCount == 0) {
1019 : // We weren't waiting on this stylesheet anyway. This can happen if
1020 : // SignalTransformEnd got called with an error aResult. See
1021 : // http://bugzilla.mozilla.org/show_bug.cgi?id=215465.
1022 0 : return NS_OK;
1023 : }
1024 :
1025 : // We're never waiting for alternate stylesheets
1026 0 : if (!aWasAlternate) {
1027 0 : --mPendingStylesheetCount;
1028 0 : SignalTransformEnd();
1029 : }
1030 :
1031 0 : return NS_OK;
1032 : }
1033 :
1034 : void
1035 0 : txTransformNotifier::Init(nsITransformObserver* aObserver)
1036 : {
1037 0 : mObserver = aObserver;
1038 0 : }
1039 :
1040 : nsresult
1041 0 : txTransformNotifier::AddScriptElement(nsIScriptElement* aElement)
1042 : {
1043 0 : return mScriptElements.AppendObject(aElement) ? NS_OK :
1044 0 : NS_ERROR_OUT_OF_MEMORY;
1045 : }
1046 :
1047 : void
1048 0 : txTransformNotifier::AddPendingStylesheet()
1049 : {
1050 0 : ++mPendingStylesheetCount;
1051 0 : }
1052 :
1053 : void
1054 0 : txTransformNotifier::OnTransformEnd(nsresult aResult)
1055 : {
1056 0 : mInTransform = false;
1057 0 : SignalTransformEnd(aResult);
1058 0 : }
1059 :
1060 : void
1061 0 : txTransformNotifier::OnTransformStart()
1062 : {
1063 0 : mInTransform = true;
1064 0 : }
1065 :
1066 : nsresult
1067 0 : txTransformNotifier::SetOutputDocument(nsIDocument* aDocument)
1068 : {
1069 0 : mDocument = aDocument;
1070 :
1071 : // Notify the contentsink that the document is created
1072 0 : return mObserver->OnDocumentCreated(mDocument);
1073 : }
1074 :
1075 : void
1076 0 : txTransformNotifier::SignalTransformEnd(nsresult aResult)
1077 : {
1078 0 : if (mInTransform ||
1079 0 : (NS_SUCCEEDED(aResult) &&
1080 0 : (mScriptElements.Count() > 0 || mPendingStylesheetCount > 0))) {
1081 0 : return;
1082 : }
1083 :
1084 : // mPendingStylesheetCount is nonzero at this point only if aResult is an
1085 : // error. Set it to 0 so we won't reenter this code when we stop the
1086 : // CSSLoader.
1087 0 : mPendingStylesheetCount = 0;
1088 0 : mScriptElements.Clear();
1089 :
1090 : // Make sure that we don't get deleted while this function is executed and
1091 : // we remove ourselfs from the scriptloader
1092 0 : nsCOMPtr<nsIScriptLoaderObserver> kungFuDeathGrip(this);
1093 :
1094 0 : if (mDocument) {
1095 0 : mDocument->ScriptLoader()->RemoveObserver(this);
1096 : // XXX Maybe we want to cancel script loads if NS_FAILED(rv)?
1097 :
1098 0 : if (NS_FAILED(aResult)) {
1099 0 : mDocument->CSSLoader()->Stop();
1100 : }
1101 : }
1102 :
1103 0 : if (NS_SUCCEEDED(aResult)) {
1104 0 : mObserver->OnTransformDone(aResult, mDocument);
1105 : }
1106 : }
|