diff --git a/src-impl/org/seasar/mayaa/impl/builder/BuilderUtil.java b/src-impl/org/seasar/mayaa/impl/builder/BuilderUtil.java index ba4606b6..2a917e3a 100644 --- a/src-impl/org/seasar/mayaa/impl/builder/BuilderUtil.java +++ b/src-impl/org/seasar/mayaa/impl/builder/BuilderUtil.java @@ -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)) { @@ -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), diff --git a/src-impl/org/seasar/mayaa/impl/builder/SpecificationNodeHandler.java b/src-impl/org/seasar/mayaa/impl/builder/SpecificationNodeHandler.java index 296ce30b..64f1a3f3 100644 --- a/src-impl/org/seasar/mayaa/impl/builder/SpecificationNodeHandler.java +++ b/src-impl/org/seasar/mayaa/impl/builder/SpecificationNodeHandler.java @@ -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); } diff --git a/src-impl/org/seasar/mayaa/impl/builder/parser/HtmlStandardScanner.java b/src-impl/org/seasar/mayaa/impl/builder/parser/HtmlStandardScanner.java index 168b5672..86ba47bc 100644 --- a/src-impl/org/seasar/mayaa/impl/builder/parser/HtmlStandardScanner.java +++ b/src-impl/org/seasar/mayaa/impl/builder/parser/HtmlStandardScanner.java @@ -1607,6 +1607,7 @@ void runTokenizer(TokenHandler handler) { handler.reportError("invalid-first-character-of-tag-name", null); tokenizeState = TokenizeState.Data; appendTextNode('<'); + pushBack(); } break; @@ -1628,7 +1629,7 @@ void runTokenizer(TokenHandler handler) { } else { handler.reportError("invalid-first-character-of-tag-name", null); tokenizeState = TokenizeState.BogusComment; - appendTextNode('<'); + pushBack(); } break; diff --git a/src/test/java/org/seasar/mayaa/functional/engine/ParserTest.java b/src/test/java/org/seasar/mayaa/functional/engine/ParserTest.java index 66f729cf..6091ce4a 100644 --- a/src/test/java/org/seasar/mayaa/functional/engine/ParserTest.java +++ b/src/test/java/org/seasar/mayaa/functional/engine/ParserTest.java @@ -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", + "\n" + + "\n" + + "
text
text
text
\n" + + "" + ); + DynamicRegisteredSourceHolder.registerContents("/expected-neko.html", + "\n" + + "\n" + + "<:div param=a>text
\n" + + "" + ); + // ウェブブラウザの解釈と同じ(エレメント名として不正な閉じタグはコメントとして付加される) + DynamicRegisteredSourceHolder.registerContents("/expected.html", + "\n" + + "\n" + + "<:div param=a>text
\n" + + "" + ); + execAndVerify("/target.html", useNewParser ? "/expected.html": "/expected-neko.html", null); + + } + } @ParameterizedTest(name = "useNewParser {0}")