Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 20 additions & 29 deletions src-impl/org/seasar/mayaa/impl/builder/BuilderUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,23 @@ static PrefixMapping getDefaultPrefixMapping() {
}

public static PrefixAwareName parseName(SpecificationNode node, String qName) {
String[] parsed = qName.split(":");
String prefix = null;
String localName = null;
URI namespaceURI = null;
String prefix = "";
String localName = qName;
URI namespaceURI = node.getQName().getNamespaceURI();
PrefixMapping mapping = null;
if (parsed.length == 2) {
prefix = parsed[0];
localName = parsed[1];
if (prefix.isEmpty()) {
// :attrName のようにプレフィックス部分が空の時は":"も属性名に含める。
// 本来のXML仕様としては許容されないがHTML仕様としては名前空間自体をサポートしていないため属性名として有効なものとした。
localName = ":" + localName;
}

int firstColonIndex = qName.indexOf(':');
if (firstColonIndex == -1) {
// コロンが存在しない場合はプレフィックスを空文字列にする。
} else if (firstColonIndex == 0) {
// コロンが先頭の文字の場合はプレフィックスを空文字列にする。コロンも含めてlocalNameとする。
// 本来のXML仕様としては許容されないがHTML仕様としては名前空間自体をサポートしていないため属性名として有効なものとした。
} else if (firstColonIndex == qName.length() - 1) {
// コロンが末尾にある場合はプレフィックスを空文字列にする。コロンも含めてlocalNameとする。
} else {
prefix = qName.substring(0, firstColonIndex);
localName = qName.substring(firstColonIndex + 1);

mapping = node.getMappingFromPrefix(prefix, true);
if (mapping == null) {
if ("xml".equals(prefix)) {
Expand All @@ -159,24 +163,11 @@ public static PrefixAwareName parseName(SpecificationNode node, String qName) {
}
}
namespaceURI = mapping.getNamespaceURI();
} else if (parsed.length == 1) {
localName = parsed[0];
namespaceURI = node.getQName().getNamespaceURI();

if (namespaceURI == null) {
mapping = node.getMappingFromPrefix("", true);
if (mapping == null) {
mapping = getDefaultPrefixMapping();
}
namespaceURI = mapping.getNamespaceURI();
}
} else {
throw new IllegalNameException(qName);
}
if (mapping != null) {
prefix = mapping.getPrefix();
} else {
prefix = "";

if (namespaceURI == null) {
mapping = getDefaultPrefixMapping();
namespaceURI = mapping.getNamespaceURI();
}
PrefixAwareName ret = SpecificationUtil.createPrefixAwareName(
SpecificationUtil.createQName(namespaceURI, localName),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,24 +217,39 @@ protected SpecificationNode addNode(QName qName) {
return addNode(qName, null, -1);
}

/**
* エレメント名のうち最初に見つかったコロンまでは、**名前空間プレフィックス**として評価が試みられます。
* ただし、そのエレメント自身、もしくは先祖エレメントで `xmlns:{プレフィックス}="{名前空間URI}"` の形式による名前空間の定義が存在しない場合は、
* コロンを含む**元の文字列全体をローカル名**として扱います(名前空間の解決を行いません)。
* エレメント名が **コロンで終わる** 場合、名前空間プレフィックスとはみなされず、**全体がローカル名**として扱われます。
*
* @param qName
* @param namespace
* @return
*/
protected SpecificationNode addNode(String qName, Namespace namespace) {
QName nodeQName;
String prefix = null;

URI namespaceURI = URIImpl.NULL_NS_URI;
int colonIndex = qName.indexOf(':');
if (colonIndex != -1) {
prefix = qName.substring(0, colonIndex);
String localName = qName.substring(colonIndex+1);
String prefix = "";
String localName = qName;
URI namespaceURI = namespace.getDefaultNamespaceURI();

int firstColonIndex = qName.indexOf(':');
if (firstColonIndex == -1) {
// コロンが存在しない場合はプレフィックスを空文字列にする。
} else if (firstColonIndex == 0) {
// コロンが先頭の文字の場合はプレフィックスを空文字列にする。コロンも含めてlocalNameとする。
// 本来のXML仕様としては許容されないがHTML仕様としては名前空間自体をサポートしていないため属性名として有効なものとした。
} else if (firstColonIndex == qName.length() - 1) {
// コロンが末尾にある場合はプレフィックスを空文字列にする。コロンも含めてlocalNameとする。
} else {
prefix = qName.substring(0, firstColonIndex);
localName = qName.substring(firstColonIndex + 1);

PrefixMapping mapping = namespace.getMappingFromPrefix(prefix, true);
if (mapping != null) {
namespaceURI = mapping.getNamespaceURI();
}
nodeQName = QNameImpl.getInstance(namespaceURI, localName);
} else {
nodeQName = QNameImpl.getInstance(namespace.getDefaultNamespaceURI(), qName);
}

QName nodeQName = QNameImpl.getInstance(namespaceURI, localName);
return addNode(nodeQName, prefix, -1);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,7 @@ void runTokenizer(TokenHandler handler) {
handler.reportError("invalid-first-character-of-tag-name", null);
tokenizeState = TokenizeState.Data;
appendTextNode('<');
pushBack();
}
break;

Expand All @@ -1628,7 +1629,7 @@ void runTokenizer(TokenHandler handler) {
} else {
handler.reportError("invalid-first-character-of-tag-name", null);
tokenizeState = TokenizeState.BogusComment;
appendTextNode('<');
pushBack();
}
break;

Expand Down
61 changes: 61 additions & 0 deletions src/test/java/org/seasar/mayaa/functional/engine/ParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,67 @@ class NotValidHtml {

execAndVerify("/target.html", useNewParser ? "/expected.html": "/expected-neko.html", null);
}

@ParameterizedTest(name = "useNewParser {0} / TagBalance {1}")
@CsvSource({"true, true", "true, false", "false, true", "false, false"})
public void エレメント名がコロンで終わったら名前空間Prefixをローカルネームとして扱う(boolean useNewParser, boolean tagBalance) throws IOException {
enableDump();
setUseNewParser(useNewParser);
setBalanceTag(tagBalance);
DynamicRegisteredSourceHolder.registerContents("/target.html",
"<!DOCTYPE html>\n" +
"<html>\n" +
"<body><section: param=a><p>text</p></section:></body>\n" +
"</html>"
);
DynamicRegisteredSourceHolder.registerContents("/expected.html",
"<!DOCTYPE html>\n" +
"<html>\n" +
"<body><section: param=\"a\"><p>text</p></section:></body>\n" +
"</html>"
);
execAndVerify("/target.html", "/expected.html", null);
}

/**
* エレメント名の先頭文字として許されない文字がある場合は次の開始タグまではテキストノードとして扱われるが
* 閉じタグの先頭文字として許されない文字の場合はHTMLコメントとして出力する。ブラウザでの解釈も同じ。
* ただし、NekoHtmlの場合は不正な閉じタグのHTMLコメント出力はしない。
*
* @param useNewParser
* @param tagBalance
* @throws IOException
* @see https://html.spec.whatwg.org/multipage/parsing.html#parse-error-invalid-first-character-of-tag-name
*/
@CsvSource({"true, true", "true, false", "false, true", "false, false"})
@ParameterizedTest(name = "useNewParser {0} / TagBalance {1}")
public void エレメント名がコロンで始まるときはエレメントとして評価されずテキストノードとして扱う_閉じタグはコメント化(boolean useNewParser, boolean tagBalance) throws IOException {
setUseNewParser(useNewParser);
setBalanceTag(tagBalance);
enableDump();
DynamicRegisteredSourceHolder.registerContents("/target.html",
"<!DOCTYPE html>\n" +
"<html>\n" +
"<body><:div param=a><p>text</p></:div></body>\n" +
"</html>"
);
DynamicRegisteredSourceHolder.registerContents("/expected-neko.html",
"<!DOCTYPE html>\n" +
"<html>\n" +
"<body><:div param=a><p>text</p></body>\n" +
"</html>"
);
// ウェブブラウザの解釈と同じ(エレメント名として不正な閉じタグはコメントとして付加される)
DynamicRegisteredSourceHolder.registerContents("/expected.html",
"<!DOCTYPE html>\n" +
"<html>\n" +
"<body><:div param=a><p>text</p><!--:div--></body>\n" +
"</html>"
);
execAndVerify("/target.html", useNewParser ? "/expected.html": "/expected-neko.html", null);

}

}

@ParameterizedTest(name = "useNewParser {0}")
Expand Down