Skip to content

Commit

Permalink
[DOXIA-734] Restore handling of XHTML 1.0 obsolete attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
kwin committed Mar 14, 2024
1 parent 6091eef commit fb55862
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,14 @@ protected void handleEntity(XmlPullParser parser, Sink sink) throws XmlPullParse
* method.
*/
protected void handleUnknown(XmlPullParser parser, Sink sink, int type) {
Object[] required = new Object[] {type};

SinkEventAttributeSet attribs = getAttributesFromParser(parser);

sink.unknown(parser.getName(), required, attribs);
handleUnknown(parser.getName(), attribs, sink, type);
}

protected void handleUnknown(String elementName, SinkEventAttributeSet attribs, Sink sink, int type) {
Object[] required = new Object[] {type};
sink.unknown(elementName, required, attribs);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.doxia.parser;

import java.util.HashMap;
import java.util.Map;
import java.util.function.UnaryOperator;

import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;

/**
* Acts as bridge between legacy parsers relying on <a href="https://www.w3.org/TR/xhtml1/">XHTML 1.0 Transitional (based on HTML4.0.1)</a>
* and the {@link Xhtml5BaseParser} only supporting HTML5 elements/attributes.
*
* Adds support for elements/attributes which <a href="https://html.spec.whatwg.org/#non-conforming-features">became obsolete in HTML5</a> but are
* commonly used for XDoc/FML.
*
* @see <a href="https://www.w3.org/TR/html5-diff/">HTML5 Differences from HTML4</a>.
*/
public class Xhtml1BaseParser extends Xhtml5BaseParser {

private static final class AttributeMapping {
private final String sourceName;
private final String targetName;
private final UnaryOperator<String> valueMapper;
private final MergeSemantics mergeSemantics;

enum MergeSemantics {
OVERWRITE,
IGNORE,
PREPEND
}

AttributeMapping(String sourceAttribute, String targetAttribute, MergeSemantics mergeSemantics) {
this(sourceAttribute, targetAttribute, UnaryOperator.identity(), mergeSemantics);
}

AttributeMapping(
String sourceName,
String targetName,
UnaryOperator<String> valueMapper,
MergeSemantics mergeSemantics) {
super();
this.sourceName = sourceName;
this.targetName = targetName;
this.valueMapper = valueMapper;
this.mergeSemantics = mergeSemantics;
}

public String getSourceName() {
return sourceName;
}

public String getTargetName() {
return targetName;
}

public UnaryOperator<String> getValueMapper() {
return valueMapper;
}

public String mergeValue(String oldValue, String newValue) {
final String mergedValue;
switch (mergeSemantics) {
case IGNORE:
mergedValue = oldValue;
break;
case OVERWRITE:
mergedValue = newValue;
break;
default:
mergedValue = newValue + " " + oldValue;
}
return mergedValue;
}
}

static final String mapAlignToStyle(String alignValue) {
switch (alignValue) {
case "center":
case "left":
case "right":
return "text-align: " + alignValue + ";";
default:
return null;
}
}

/**
* All obsolete attributes in a map with key = affected element name, value = {@link AttributeMapping}
*/
private static final Map<String, AttributeMapping> ATTRIBUTE_MAPPING_TABLE = new HashMap<>();

static {
ATTRIBUTE_MAPPING_TABLE.put("a", new AttributeMapping("name", "id", AttributeMapping.MergeSemantics.IGNORE));
ATTRIBUTE_MAPPING_TABLE.put(
"table",
new AttributeMapping(
"border",
"class",
(v) -> (v != null && !v.equals("0")) ? "bodyTableBorder" : null,
AttributeMapping.MergeSemantics.PREPEND));
ATTRIBUTE_MAPPING_TABLE.put(
"table",
new AttributeMapping(
"align", "style", Xhtml1BaseParser::mapAlignToStyle, AttributeMapping.MergeSemantics.PREPEND));
ATTRIBUTE_MAPPING_TABLE.put(
"td",
new AttributeMapping(
"align", "style", Xhtml1BaseParser::mapAlignToStyle, AttributeMapping.MergeSemantics.PREPEND));
ATTRIBUTE_MAPPING_TABLE.put(
"th",
new AttributeMapping(
"align", "style", Xhtml1BaseParser::mapAlignToStyle, AttributeMapping.MergeSemantics.PREPEND));
}

/**
* Translates obsolete XHTML 1.0 attributes to valid XHTML5 ones before calling the underlying {@link Xhtml5BaseParser}.
*/
@Override
protected boolean baseStartTag(XmlPullParser parser, Sink sink) {
SinkEventAttributeSet attribs = getAttributesFromParser(parser);
String elementName = parser.getName();
AttributeMapping attributeMapping = ATTRIBUTE_MAPPING_TABLE.get(elementName);
if (attributeMapping != null) {
String attributeValue = (String) attribs.getAttribute(attributeMapping.getSourceName());
if (attributeValue != null) {
String newValue = attributeMapping.getValueMapper().apply(attributeValue);
if (newValue != null) {
String oldValue = (String) attribs.getAttribute(attributeMapping.getTargetName());
if (oldValue != null) {
newValue = attributeMapping.mergeValue(oldValue, newValue);
}
attribs.addAttribute(attributeMapping.getTargetName(), newValue);
}
attribs.removeAttribute(attributeMapping.getSourceName());
}
}
return super.baseStartTag(elementName, attribs, sink);
}
}

0 comments on commit fb55862

Please sign in to comment.