diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts
index 9d595b684ec4..fc9c9a234daa 100644
--- a/modules/angular2/src/compiler/html_parser.ts
+++ b/modules/angular2/src/compiler/html_parser.ts
@@ -5,7 +5,6 @@ import {
stringify,
assertionsEnabled,
StringJoiner,
- RegExpWrapper,
serializeEnum,
CONST_EXPR
} from 'angular2/src/facade/lang';
@@ -17,7 +16,7 @@ import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast';
import {Injectable} from 'angular2/src/core/di';
import {HtmlToken, HtmlTokenType, tokenizeHtml} from './html_lexer';
import {ParseError, ParseLocation, ParseSourceSpan} from './parse_util';
-import {HtmlTagDefinition, getHtmlTagDefinition} from './html_tags';
+import {HtmlTagDefinition, getHtmlTagDefinition, getHtmlTagNamespacePrefix} from './html_tags';
export class HtmlTreeError extends ParseError {
static create(elementName: string, location: ParseLocation, msg: string): HtmlTreeError {
@@ -134,7 +133,7 @@ class TreeBuilder {
if (this.peek.type === HtmlTokenType.TAG_OPEN_END_VOID) {
this._advance();
selfClosing = true;
- if (namespacePrefix(fullName) == null && !getHtmlTagDefinition(fullName).isVoid) {
+ if (getHtmlTagNamespacePrefix(fullName) == null && !getHtmlTagDefinition(fullName).isVoid) {
this.errors.push(HtmlTreeError.create(
fullName, startTagToken.sourceSpan.start,
`Only void and foreign elements can be self closed "${startTagToken.parts[1]}"`));
@@ -237,16 +236,9 @@ function getElementFullName(prefix: string, localName: string,
if (isBlank(prefix)) {
prefix = getHtmlTagDefinition(localName).implicitNamespacePrefix;
if (isBlank(prefix) && isPresent(parentElement)) {
- prefix = namespacePrefix(parentElement.name);
+ prefix = getHtmlTagNamespacePrefix(parentElement.name);
}
}
return mergeNsAndName(prefix, localName);
}
-
-var NS_PREFIX_RE = /^@([^:]+)/g;
-
-function namespacePrefix(elementName: string): string {
- var match = RegExpWrapper.firstMatch(NS_PREFIX_RE, elementName);
- return isBlank(match) ? null : match[1];
-}
diff --git a/modules/angular2/src/compiler/html_tags.ts b/modules/angular2/src/compiler/html_tags.ts
index 1253b94ae204..952eca4e52d4 100644
--- a/modules/angular2/src/compiler/html_tags.ts
+++ b/modules/angular2/src/compiler/html_tags.ts
@@ -1,4 +1,10 @@
-import {isPresent, isBlank, normalizeBool, CONST_EXPR} from 'angular2/src/facade/lang';
+import {
+ isPresent,
+ isBlank,
+ normalizeBool,
+ RegExpWrapper,
+ CONST_EXPR
+} from 'angular2/src/facade/lang';
// see http://www.w3.org/TR/html51/syntax.html#named-character-references
// see https://html.spec.whatwg.org/multipage/entities.json
@@ -381,3 +387,17 @@ export function getHtmlTagDefinition(tagName: string): HtmlTagDefinition {
var result = TAG_DEFINITIONS[tagName.toLowerCase()];
return isPresent(result) ? result : DEFAULT_TAG_DEFINITION;
}
+
+var NS_PREFIX_RE = /^@([^:]+):(.+)/g;
+
+export function splitHtmlTagNamespace(elementName: string): string[] {
+ if (elementName[0] != '@') {
+ return [null, elementName];
+ }
+ let match = RegExpWrapper.firstMatch(NS_PREFIX_RE, elementName);
+ return [match[1], match[2]];
+}
+
+export function getHtmlTagNamespacePrefix(elementName: string): string {
+ return splitHtmlTagNamespace(elementName)[0];
+}
diff --git a/modules/angular2/src/compiler/schema/dom_element_schema_registry.ts b/modules/angular2/src/compiler/schema/dom_element_schema_registry.ts
index 86d825022d2a..751e420a4300 100644
--- a/modules/angular2/src/compiler/schema/dom_element_schema_registry.ts
+++ b/modules/angular2/src/compiler/schema/dom_element_schema_registry.ts
@@ -1,10 +1,14 @@
import {Injectable} from 'angular2/src/core/di';
-import {isPresent, isBlank} from 'angular2/src/facade/lang';
+import {isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/platform/dom/dom_adapter';
+import {splitHtmlTagNamespace} from 'angular2/src/compiler/html_tags';
import {ElementSchemaRegistry} from './element_schema_registry';
+const NAMESPACE_URIS =
+ CONST_EXPR({'xlink': 'http://www.w3.org/1999/xlink', 'svg': 'http://www.w3.org/2000/svg'});
+
@Injectable()
export class DomElementSchemaRegistry extends ElementSchemaRegistry {
private _protoElements = new Map();
@@ -12,7 +16,10 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
private _getProtoElement(tagName: string): Element {
var element = this._protoElements.get(tagName);
if (isBlank(element)) {
- element = DOM.createElement(tagName);
+ var nsAndName = splitHtmlTagNamespace(tagName);
+ element = isPresent(nsAndName[0]) ?
+ DOM.createElementNS(NAMESPACE_URIS[nsAndName[0]], nsAndName[1]) :
+ DOM.createElement(nsAndName[1]);
this._protoElements.set(tagName, element);
}
return element;
diff --git a/modules/angular2/src/platform/server/parse5_adapter.ts b/modules/angular2/src/platform/server/parse5_adapter.ts
index 9621e04f8214..78d45a47fdeb 100644
--- a/modules/angular2/src/platform/server/parse5_adapter.ts
+++ b/modules/angular2/src/platform/server/parse5_adapter.ts
@@ -274,7 +274,7 @@ export class Parse5DomAdapter extends DomAdapter {
createElement(tagName): HTMLElement {
return treeAdapter.createElement(tagName, 'http://www.w3.org/1999/xhtml', []);
}
- createElementNS(ns, tagName): HTMLElement { throw 'not implemented'; }
+ createElementNS(ns, tagName): HTMLElement { return treeAdapter.createElement(tagName, ns, []); }
createTextNode(text: string): Text {
var t = this.createComment(text);
t.type = 'text';
diff --git a/modules/angular2/test/compiler/schema/dom_element_schema_registry_spec.ts b/modules/angular2/test/compiler/schema/dom_element_schema_registry_spec.ts
index 2b2654074231..57c32b8a7660 100644
--- a/modules/angular2/test/compiler/schema/dom_element_schema_registry_spec.ts
+++ b/modules/angular2/test/compiler/schema/dom_element_schema_registry_spec.ts
@@ -40,5 +40,8 @@ export function main() {
expect(registry.getMappedPropName('title')).toEqual('title');
expect(registry.getMappedPropName('exotic-unknown')).toEqual('exotic-unknown');
});
+
+ it('should detect properties on namespaced elements',
+ () => { expect(registry.hasProperty('@svg:g', 'id')).toBeTruthy(); });
});
}
diff --git a/modules/playground/e2e_test/svg/svg_spec.dart b/modules/playground/e2e_test/svg/svg_spec.dart
new file mode 100644
index 000000000000..2d253782acf7
--- /dev/null
+++ b/modules/playground/e2e_test/svg/svg_spec.dart
@@ -0,0 +1,3 @@
+library playground.e2e_test.svg.svg_spec;
+
+main() {}
diff --git a/modules/playground/e2e_test/svg/svg_spec.ts b/modules/playground/e2e_test/svg/svg_spec.ts
new file mode 100644
index 000000000000..40bb4958e061
--- /dev/null
+++ b/modules/playground/e2e_test/svg/svg_spec.ts
@@ -0,0 +1,15 @@
+import {verifyNoBrowserErrors} from 'angular2/src/testing/e2e_util';
+
+describe('SVG', function() {
+
+ var URL = 'playground/src/svg/index.html';
+
+ afterEach(verifyNoBrowserErrors);
+ beforeEach(() => { browser.get(URL); });
+
+ it('should display SVG component contents', function() {
+ var svgText = element.all(by.css('g text')).get(0);
+ expect(svgText.getText()).toEqual('Hello');
+ });
+
+});
diff --git a/modules/playground/pubspec.yaml b/modules/playground/pubspec.yaml
index 97d9bdd140be..35538a25aa09 100644
--- a/modules/playground/pubspec.yaml
+++ b/modules/playground/pubspec.yaml
@@ -31,6 +31,7 @@ transformers:
- web/src/routing/index.dart
- web/src/template_driven_forms/index.dart
- web/src/zippy_component/index.dart
+ - web/src/svg/index.dart
- web/src/material/button/index.dart
- web/src/material/checkbox/index.dart
- web/src/material/dialog/index.dart
diff --git a/modules/playground/src/svg/index.html b/modules/playground/src/svg/index.html
new file mode 100644
index 000000000000..9b2f3abd40e9
--- /dev/null
+++ b/modules/playground/src/svg/index.html
@@ -0,0 +1,10 @@
+
+
+ SVG
+
+
+ Loading...
+
+ $SCRIPTS$
+
+
diff --git a/modules/playground/src/svg/index.ts b/modules/playground/src/svg/index.ts
new file mode 100644
index 000000000000..e87641bc8abf
--- /dev/null
+++ b/modules/playground/src/svg/index.ts
@@ -0,0 +1,22 @@
+import {bootstrap} from 'angular2/bootstrap';
+import {Component} from 'angular2/core';
+
+@Component({selector: '[svg-group]', template: `Hello`})
+class SvgGroup {
+}
+
+
+@Component({
+ selector: 'svg-app',
+ template: ``,
+ directives: [SvgGroup]
+})
+class SvgApp {
+}
+
+
+export function main() {
+ bootstrap(SvgApp);
+}
diff --git a/tools/broccoli/trees/browser_tree.ts b/tools/broccoli/trees/browser_tree.ts
index 51d2a85bbba3..443808e459ea 100644
--- a/tools/broccoli/trees/browser_tree.ts
+++ b/tools/broccoli/trees/browser_tree.ts
@@ -51,6 +51,7 @@ const kServedPaths = [
'playground/src/key_events',
'playground/src/routing',
'playground/src/sourcemap',
+ 'playground/src/svg',
'playground/src/todo',
'playground/src/upgrade',
'playground/src/zippy_component',