blob: a8a4aa78e30c14ab65ac11216c39eb4080a4d519 [file] [log] [blame]
/*
* This file is part of the XSL implementation.
*
* Copyright (C) 2004, 2005, 2006, 2008, 2012 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "core/xml/XSLStyleSheet.h"
#include <libxml/uri.h>
#include <libxslt/xsltutils.h>
#include "core/dom/Document.h"
#include "core/dom/Node.h"
#include "core/dom/TransformSource.h"
#include "core/frame/LocalFrame.h"
#include "core/loader/resource/XSLStyleSheetResource.h"
#include "core/xml/XSLTProcessor.h"
#include "core/xml/parser/XMLDocumentParserScope.h"
#include "core/xml/parser/XMLParserInput.h"
#include "platform/loader/fetch/FetchParameters.h"
#include "platform/loader/fetch/fetch_initiator_type_names.h"
#include "platform/wtf/text/CString.h"
namespace blink {
XSLStyleSheet::XSLStyleSheet(XSLStyleSheet* parent_style_sheet,
const String& original_url,
const KURL& final_url)
: owner_node_(nullptr),
original_url_(original_url),
final_url_(final_url),
is_disabled_(false),
embedded_(false),
// Child sheets get marked as processed when the libxslt engine has
// finally seen them.
processed_(false),
stylesheet_doc_(nullptr),
stylesheet_doc_taken_(false),
compilation_failed_(false),
parent_style_sheet_(parent_style_sheet),
owner_document_(nullptr) {}
XSLStyleSheet::XSLStyleSheet(Node* parent_node,
const String& original_url,
const KURL& final_url,
bool embedded)
: owner_node_(parent_node),
original_url_(original_url),
final_url_(final_url),
is_disabled_(false),
embedded_(embedded),
processed_(true), // The root sheet starts off processed.
stylesheet_doc_(nullptr),
stylesheet_doc_taken_(false),
compilation_failed_(false),
parent_style_sheet_(nullptr),
owner_document_(nullptr) {}
XSLStyleSheet::XSLStyleSheet(Document* owner_document,
Node* style_sheet_root_node,
const String& original_url,
const KURL& final_url,
bool embedded)
: owner_node_(style_sheet_root_node),
original_url_(original_url),
final_url_(final_url),
is_disabled_(false),
embedded_(embedded),
processed_(true), // The root sheet starts off processed.
stylesheet_doc_(nullptr),
stylesheet_doc_taken_(false),
compilation_failed_(false),
parent_style_sheet_(nullptr),
owner_document_(owner_document) {}
XSLStyleSheet::~XSLStyleSheet() {
if (!stylesheet_doc_taken_)
xmlFreeDoc(stylesheet_doc_);
}
void XSLStyleSheet::CheckLoaded() {
if (XSLStyleSheet* style_sheet = parentStyleSheet())
style_sheet->CheckLoaded();
if (ownerNode())
ownerNode()->SheetLoaded();
}
xmlDocPtr XSLStyleSheet::GetDocument() {
if (embedded_ && OwnerDocument() && OwnerDocument()->GetTransformSource())
return (xmlDocPtr)OwnerDocument()->GetTransformSource()->PlatformSource();
return stylesheet_doc_;
}
void XSLStyleSheet::ClearDocuments() {
stylesheet_doc_ = nullptr;
for (unsigned i = 0; i < children_.size(); ++i)
children_.at(i)->ClearDocuments();
}
bool XSLStyleSheet::ParseString(const String& source) {
// Parse in a single chunk into an xmlDocPtr
if (!stylesheet_doc_taken_)
xmlFreeDoc(stylesheet_doc_);
stylesheet_doc_taken_ = false;
FrameConsole* console = nullptr;
if (LocalFrame* frame = OwnerDocument()->GetFrame())
console = &frame->Console();
XMLDocumentParserScope scope(OwnerDocument(), XSLTProcessor::GenericErrorFunc,
XSLTProcessor::ParseErrorFunc, console);
XMLParserInput input(source);
xmlParserCtxtPtr ctxt = xmlCreateMemoryParserCtxt(input.Data(), input.size());
if (!ctxt)
return 0;
if (parent_style_sheet_) {
// The XSL transform may leave the newly-transformed document
// with references to the symbol dictionaries of the style sheet
// and any of its children. XML document disposal can corrupt memory
// if a document uses more than one symbol dictionary, so we
// ensure that all child stylesheets use the same dictionaries as their
// parents.
xmlDictFree(ctxt->dict);
ctxt->dict = parent_style_sheet_->stylesheet_doc_->dict;
xmlDictReference(ctxt->dict);
}
stylesheet_doc_ =
xmlCtxtReadMemory(ctxt, input.Data(), input.size(),
final_url_.GetString().Utf8().data(), input.Encoding(),
XML_PARSE_NOENT | XML_PARSE_DTDATTR |
XML_PARSE_NOWARNING | XML_PARSE_NOCDATA);
xmlFreeParserCtxt(ctxt);
LoadChildSheets();
return stylesheet_doc_;
}
void XSLStyleSheet::LoadChildSheets() {
if (!GetDocument())
return;
xmlNodePtr stylesheet_root = GetDocument()->children;
// Top level children may include other things such as DTD nodes, we ignore
// those.
while (stylesheet_root && stylesheet_root->type != XML_ELEMENT_NODE)
stylesheet_root = stylesheet_root->next;
if (embedded_) {
// We have to locate (by ID) the appropriate embedded stylesheet
// element, so that we can walk the import/include list.
xmlAttrPtr id_node = xmlGetID(
GetDocument(), (const xmlChar*)(final_url_.GetString().Utf8().data()));
if (!id_node)
return;
stylesheet_root = id_node->parent;
} else {
// FIXME: Need to handle an external URI with a # in it. This is a
// pretty minor edge case, so we'll deal with it later.
}
if (stylesheet_root) {
// Walk the children of the root element and look for import/include
// elements. Imports must occur first.
xmlNodePtr curr = stylesheet_root->children;
while (curr) {
if (curr->type != XML_ELEMENT_NODE) {
curr = curr->next;
continue;
}
if (IS_XSLT_ELEM(curr) && IS_XSLT_NAME(curr, "import")) {
xmlChar* uri_ref =
xsltGetNsProp(curr, (const xmlChar*)"href", XSLT_NAMESPACE);
LoadChildSheet(String::FromUTF8((const char*)uri_ref));
xmlFree(uri_ref);
} else {
break;
}
curr = curr->next;
}
// Now handle includes.
while (curr) {
if (curr->type == XML_ELEMENT_NODE && IS_XSLT_ELEM(curr) &&
IS_XSLT_NAME(curr, "include")) {
xmlChar* uri_ref =
xsltGetNsProp(curr, (const xmlChar*)"href", XSLT_NAMESPACE);
LoadChildSheet(String::FromUTF8((const char*)uri_ref));
xmlFree(uri_ref);
}
curr = curr->next;
}
}
}
void XSLStyleSheet::LoadChildSheet(const String& href) {
// Use parent styleheet's URL as the base URL
KURL url(BaseURL(), href);
// Check for a cycle in our import chain. If we encounter a stylesheet in
// our parent chain with the same URL, then just bail.
for (XSLStyleSheet* parent_sheet = parentStyleSheet(); parent_sheet;
parent_sheet = parent_sheet->parentStyleSheet()) {
if (url == parent_sheet->BaseURL())
return;
}
const String& url_string = url.GetString();
ResourceLoaderOptions fetch_options;
fetch_options.initiator_info.name = FetchInitiatorTypeNames::xml;
FetchParameters params(
ResourceRequest(OwnerDocument()->CompleteURL(url_string)), fetch_options);
params.SetOriginRestriction(FetchParameters::kRestrictToSameOrigin);
XSLStyleSheetResource* resource = XSLStyleSheetResource::FetchSynchronously(
params, OwnerDocument()->Fetcher());
if (!resource || !resource->Sheet())
return;
XSLStyleSheet* style_sheet =
new XSLStyleSheet(this, url_string, resource->GetResponse().Url());
children_.push_back(style_sheet);
style_sheet->ParseString(resource->Sheet());
CheckLoaded();
}
xsltStylesheetPtr XSLStyleSheet::CompileStyleSheet() {
// FIXME: Hook up error reporting for the stylesheet compilation process.
if (embedded_)
return xsltLoadStylesheetPI(GetDocument());
// Certain libxslt versions are corrupting the xmlDoc on compilation
// failures - hence attempting to recompile after a failure is unsafe.
if (compilation_failed_)
return nullptr;
// xsltParseStylesheetDoc makes the document part of the stylesheet
// so we have to release our pointer to it.
DCHECK(!stylesheet_doc_taken_);
xsltStylesheetPtr result = xsltParseStylesheetDoc(stylesheet_doc_);
if (result)
stylesheet_doc_taken_ = true;
else
compilation_failed_ = true;
return result;
}
Document* XSLStyleSheet::OwnerDocument() {
for (XSLStyleSheet* style_sheet = this; style_sheet;
style_sheet = style_sheet->parentStyleSheet()) {
if (style_sheet->owner_document_)
return style_sheet->owner_document_.Get();
Node* node = style_sheet->ownerNode();
if (node)
return &node->GetDocument();
}
return nullptr;
}
xmlDocPtr XSLStyleSheet::LocateStylesheetSubResource(xmlDocPtr parent_doc,
const xmlChar* uri) {
bool matched_parent = (parent_doc == GetDocument());
for (unsigned i = 0; i < children_.size(); ++i) {
XSLStyleSheet* child = children_.at(i).Get();
if (matched_parent) {
if (child->Processed())
continue; // libxslt has been given this sheet already.
// Check the URI of the child stylesheet against the doc URI.
// In order to ensure that libxml canonicalized both URLs, we get
// the original href string from the import rule and canonicalize it
// using libxml before comparing it with the URI argument.
CString import_href = child->href().Utf8();
xmlChar* base = xmlNodeGetBase(parent_doc, (xmlNodePtr)parent_doc);
xmlChar* child_uri =
xmlBuildURI((const xmlChar*)import_href.data(), base);
bool equal_ur_is = xmlStrEqual(uri, child_uri);
xmlFree(base);
xmlFree(child_uri);
if (equal_ur_is) {
child->MarkAsProcessed();
return child->GetDocument();
}
continue;
}
xmlDocPtr result = child->LocateStylesheetSubResource(parent_doc, uri);
if (result)
return result;
}
return nullptr;
}
void XSLStyleSheet::MarkAsProcessed() {
DCHECK(!processed_);
DCHECK(!stylesheet_doc_taken_);
processed_ = true;
stylesheet_doc_taken_ = true;
}
void XSLStyleSheet::Trace(blink::Visitor* visitor) {
visitor->Trace(owner_node_);
visitor->Trace(children_);
visitor->Trace(parent_style_sheet_);
visitor->Trace(owner_document_);
StyleSheet::Trace(visitor);
}
} // namespace blink