Skip to content

Commit

Permalink
[DOXIA-734] Restore handling of XHTML 1.0 obsolete attributes/elements
Browse files Browse the repository at this point in the history
  • Loading branch information
kwin committed Mar 15, 2024
1 parent 6091eef commit dc29416
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 172 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,180 @@
/*
* 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.Arrays;
import java.util.Collection;
import java.util.Collections;
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)</a>
* and the {@link Xhtml5BaseParser} only supporting (X)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 = collection if {@link AttributeMapping}s
*/
private static final Map<String, Collection<AttributeMapping>> ATTRIBUTE_MAPPING_TABLE = new HashMap<>();

/**
* All obsolete elements in a map with key = obsolete element name, value = non-obsolete replacement element name
*/
private static final Map<String, String> ELEMENT_MAPPING_TABLE = new HashMap<>();

static {
ATTRIBUTE_MAPPING_TABLE.put(
"a", Collections.singleton(new AttributeMapping("name", "id", AttributeMapping.MergeSemantics.IGNORE)));

Collection<AttributeMapping> tableMappings = Arrays.asList(
new AttributeMapping(
"border",
"class",
(v) -> (v != null && !v.equals("0")) ? "bodyTableBorder" : null,
AttributeMapping.MergeSemantics.PREPEND),
new AttributeMapping(
"align", "style", Xhtml1BaseParser::mapAlignToStyle, AttributeMapping.MergeSemantics.PREPEND));
ATTRIBUTE_MAPPING_TABLE.put("table", tableMappings);
ATTRIBUTE_MAPPING_TABLE.put(
"td",
Collections.singleton(new AttributeMapping(
"align", "style", Xhtml1BaseParser::mapAlignToStyle, AttributeMapping.MergeSemantics.PREPEND)));
ATTRIBUTE_MAPPING_TABLE.put(
"th",
Collections.singleton(new AttributeMapping(
"align", "style", Xhtml1BaseParser::mapAlignToStyle, AttributeMapping.MergeSemantics.PREPEND)));
ELEMENT_MAPPING_TABLE.put("tt", "code");
ELEMENT_MAPPING_TABLE.put("strike", "del");
}

/**
* Translates obsolete XHTML 1.0 attributes/elements 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();
Collection<AttributeMapping> attributeMappings = ATTRIBUTE_MAPPING_TABLE.get(elementName);
if (attributeMappings != null) {
for (AttributeMapping attributeMapping : attributeMappings) {
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());
}
}
}
String mappedElementName = ELEMENT_MAPPING_TABLE.getOrDefault(elementName, elementName);
return super.baseStartTag(mappedElementName, attribs, sink);
}

@Override
protected boolean baseEndTag(XmlPullParser parser, Sink sink) {
SinkEventAttributeSet attribs = getAttributesFromParser(parser);
String elementName = parser.getName();
String mappedElementName = ELEMENT_MAPPING_TABLE.getOrDefault(elementName, elementName);
return super.baseEndTag(mappedElementName, attribs, sink);
}
}

0 comments on commit dc29416

Please sign in to comment.