From 63575df2b01ca570d630d2a7a114f93ed649a7a7 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Tue, 7 Dec 2021 19:32:28 -0500 Subject: [PATCH] [Java.Interop.Tools.JavaSource] Add {@ docRoot} support Partially fixes: https://github.com/xamarin/java.interop/issues/907 Support for `{@ docRoot}` has been added, and an additional parser for `` elements has been added to convert these to `` elements. --- ...urceJavadocToXmldocGrammar.HtmlBnfTerms.cs | 38 +++++++++++++++++++ ...vadocToXmldocGrammar.InlineTagsBnfTerms.cs | 10 ++++- .../SourceJavadocToXmldocGrammar.cs | 8 ++-- .../SourceJavadocToXmldocParser.cs | 24 ++++++------ .../XmldocSettings.cs | 12 ++++++ ...avadocToXmldocGrammar.HtmlBnfTermsTests.cs | 16 ++++++++ ...ToXmldocGrammar.InlineTagsBnfTermsTests.cs | 2 +- .../SourceJavadocToXmldocGrammarFixture.cs | 9 ++++- .../SourceJavadocToXmldocParserTests.cs | 32 +++++++++++++++- .../JavadocInfo.cs | 19 +++++++--- .../main/java/com/microsoft/android/App.java | 2 +- .../android/JavaSourceUtilsOptions.java | 8 ++++ .../android/JavadocXmlGenerator.java | 16 +++++--- 13 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/XmldocSettings.cs diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.HtmlBnfTerms.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.HtmlBnfTerms.cs index f4e16b01d..84735783d 100644 --- a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.HtmlBnfTerms.cs +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.HtmlBnfTerms.cs @@ -33,6 +33,7 @@ internal void CreateRules (SourceJavadocToXmldocGrammar grammar) | SpecialDeclaration | FormCtrlDeclaration */ + | InlineHyperLinkDeclaration | grammar.InlineTagsTerms.AllInlineTerms | UnknownHtmlElementStart , @@ -94,6 +95,36 @@ internal void CreateRules (SourceJavadocToXmldocGrammar grammar) FinishParse (context, parseNode).Remarks.Add (p); parseNode.AstNode = p; }; + + InlineHyperLinkDeclaration.Rule = InlineHyperLinkOpenTerm + InlineDeclarations + CreateEndElement ("a", grammar, optional: true); + InlineHyperLinkDeclaration.AstConfig.NodeCreator = (context, parseNode) => { + var unparsedAElementValue = string.Empty; + foreach (var cn in parseNode.ChildNodes) { + if (cn.ChildNodes?.Count > 1) { + foreach (var gcn in cn.ChildNodes) { + unparsedAElementValue += gcn.AstNode?.ToString (); + } + } else { + unparsedAElementValue += cn.AstNode?.ToString (); + } + } + + XNode astNodeElement = new XText (unparsedAElementValue); + try { + var seeElement = XElement.Parse ($""); + var hrefValue = seeElement.Attribute ("href")?.Value ?? string.Empty; + if (!string.IsNullOrEmpty (hrefValue) && + (hrefValue.StartsWith ("http", StringComparison.OrdinalIgnoreCase) || hrefValue.StartsWith ("www", StringComparison.OrdinalIgnoreCase))) { + parseNode.AstNode = seeElement; + } else { + // TODO: Need to convert relative paths or code references to appropriate CREF value. + parseNode.AstNode = astNodeElement; + } + } catch (Exception) { + Console.Error.WriteLine ($"# Unable to parse HTML element: "); + parseNode.AstNode = astNodeElement; + } + }; } static IEnumerable GetParagraphs (ParseTreeNodeList children) @@ -161,6 +192,13 @@ static IEnumerable GetParagraphs (ParseTreeNodeList children) public readonly NonTerminal BlockDeclaration = new NonTerminal (nameof (BlockDeclaration), ConcatChildNodes); public readonly NonTerminal PBlockDeclaration = new NonTerminal (nameof (PBlockDeclaration), ConcatChildNodes); public readonly NonTerminal PreBlockDeclaration = new NonTerminal (nameof (PreBlockDeclaration), ConcatChildNodes); + public readonly NonTerminal InlineHyperLinkDeclaration = new NonTerminal (nameof (InlineHyperLinkDeclaration), ConcatChildNodes); + + public readonly Terminal InlineHyperLinkOpenTerm = new RegexBasedTerminal (" { - parseNode.AstNode = new XText ("[TODO: @docRoot]"); + var docRoot = grammar.XmldocSettings.DocRootValue; + if (!string.IsNullOrEmpty (docRoot)) { + if (!docRoot.EndsWith ("/", StringComparison.OrdinalIgnoreCase)) { + docRoot += "/"; + } + } else { + docRoot = "{@docRoot}"; + } + parseNode.AstNode = new XText (docRoot); }; InheritDocDeclaration.Rule = grammar.ToTerm ("{@inheritDoc}"); diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.cs index a2be927b5..5f5379550 100644 --- a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.cs +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocGrammar.cs @@ -16,15 +16,15 @@ public partial class SourceJavadocToXmldocGrammar : Grammar { public readonly InlineTagsBnfTerms InlineTagsTerms; public readonly HtmlBnfTerms HtmlTerms; - public readonly XmldocStyle XmldocStyle; + public readonly XmldocSettings XmldocSettings; - public SourceJavadocToXmldocGrammar (XmldocStyle style) + public SourceJavadocToXmldocGrammar (XmldocSettings settings) { BlockTagsTerms = new BlockTagsBnfTerms (); InlineTagsTerms = new InlineTagsBnfTerms (); HtmlTerms = new HtmlBnfTerms (); - XmldocStyle = style; + XmldocSettings = settings; BlockTagsTerms.CreateRules (this); InlineTagsTerms.CreateRules (this); @@ -55,7 +55,7 @@ public SourceJavadocToXmldocGrammar (XmldocStyle style) internal bool ShouldImport (ImportJavadoc value) { - var v = (ImportJavadoc) XmldocStyle; + var v = (ImportJavadoc) XmldocSettings.Style; return v.HasFlag (value); } diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocParser.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocParser.cs index b50d8b514..4f29894cb 100644 --- a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocParser.cs +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/SourceJavadocToXmldocParser.cs @@ -56,19 +56,19 @@ public enum XmldocStyle { public class SourceJavadocToXmldocParser : Irony.Parsing.Parser { - public SourceJavadocToXmldocParser (XmldocStyle style = XmldocStyle.Full) - : base (CreateGrammar (style)) + public SourceJavadocToXmldocParser (XmldocSettings settings) + : base (CreateGrammar (settings)) { - XmldocStyle = style; + XmldocSettings = settings; } - public XmldocStyle XmldocStyle { get; } + public XmldocSettings XmldocSettings { get; } - public XElement[]? ExtraRemarks { get; set; } - static Grammar CreateGrammar (XmldocStyle style) + + static Grammar CreateGrammar (XmldocSettings settings) { - return new SourceJavadocToXmldocGrammar (style) { + return new SourceJavadocToXmldocGrammar (settings) { LanguageFlags = LanguageFlags.Default | LanguageFlags.CreateAst, }; } @@ -102,13 +102,13 @@ IEnumerable CreateParseIterator (ParseTree parseTree) var summary = CreateSummaryNode (info); if (summary != null) yield return summary; - var style = (ImportJavadoc) XmldocStyle; + var style = (ImportJavadoc) XmldocSettings.Style; if (style.HasFlag (ImportJavadoc.Remarks) && - (info.Remarks.Count > 0 || ExtraRemarks?.Length > 0)) { - yield return new XElement ("remarks", info.Remarks, ExtraRemarks); + (info.Remarks.Count > 0 || XmldocSettings.ExtraRemarks?.Length > 0)) { + yield return new XElement ("remarks", info.Remarks, XmldocSettings.ExtraRemarks); } - else if (style.HasFlag (ImportJavadoc.ExtraRemarks) && ExtraRemarks?.Length > 0) { - yield return new XElement ("remarks", ExtraRemarks); + else if (style.HasFlag (ImportJavadoc.ExtraRemarks) && XmldocSettings.ExtraRemarks?.Length > 0) { + yield return new XElement ("remarks", XmldocSettings.ExtraRemarks); } foreach (var n in info.Returns) { yield return n; diff --git a/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/XmldocSettings.cs b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/XmldocSettings.cs new file mode 100644 index 000000000..4906ceed6 --- /dev/null +++ b/src/Java.Interop.Tools.JavaSource/Java.Interop.Tools.JavaSource/XmldocSettings.cs @@ -0,0 +1,12 @@ +using System; +using System.Xml.Linq; + +namespace Java.Interop.Tools.JavaSource +{ + public class XmldocSettings + { + public string DocRootValue { get; set; } = string.Empty; + public XElement []? ExtraRemarks { get; set; } + public XmldocStyle Style { get; set; } = XmldocStyle.Full; + } +} diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.HtmlBnfTermsTests.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.HtmlBnfTermsTests.cs index 575a2ce8f..643782f69 100644 --- a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.HtmlBnfTermsTests.cs +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.HtmlBnfTermsTests.cs @@ -47,5 +47,21 @@ public void PreBlockDeclaration () r.Root.AstNode.ToString ()); } + + [Test] + public void HyperLinkDeclaration () + { + var p = CreateParser (g => g.HtmlTerms.InlineHyperLinkDeclaration); + + var r = p.Parse ("application"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r, p)); + Assert.AreEqual ("application", + r.Root.AstNode.ToString ()); + + r = p.Parse ("field classification"); + Assert.IsFalse (r.HasErrors (), DumpMessages (r, p)); + Assert.AreEqual ("\"AutofillService.html#FieldClassification\">field classification", + r.Root.AstNode.ToString ()); + } } } diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.InlineTagsBnfTermsTests.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.InlineTagsBnfTermsTests.cs index 3bb4f9095..869a90d6c 100644 --- a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.InlineTagsBnfTermsTests.cs +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammar.InlineTagsBnfTermsTests.cs @@ -31,7 +31,7 @@ public void DocRootDeclaration () var r = p.Parse ("{@docRoot}"); Assert.IsFalse (r.HasErrors (), DumpMessages (r, p)); - Assert.AreEqual ("[TODO: @docRoot]", r.Root.AstNode.ToString ()); + Assert.AreEqual (DocRootPrefixExpected, r.Root.AstNode.ToString ()); } [Test] diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammarFixture.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammarFixture.cs index c8e52448a..3362db412 100644 --- a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammarFixture.cs +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocGrammarFixture.cs @@ -17,9 +17,16 @@ namespace Java.Interop.Tools.JavaSource.Tests [TestFixture] public class SourceJavadocToXmldocGrammarFixture { + protected const string DocRootPrefixActual = "https://developer.android.com"; + protected const string DocRootPrefixExpected = DocRootPrefixActual + "/"; + public static Parser CreateParser (Func root) { - var g = new SourceJavadocToXmldocGrammar (XmldocStyle.Full) { + var g = new SourceJavadocToXmldocGrammar (new XmldocSettings { + Style = XmldocStyle.Full, + DocRootValue = DocRootPrefixActual, + }) + { LanguageFlags = LanguageFlags.Default | LanguageFlags.CreateAst, }; g.Root = root (g); diff --git a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocParserTests.cs b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocParserTests.cs index e0480ab83..fa951fc4d 100644 --- a/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocParserTests.cs +++ b/tests/Java.Interop.Tools.JavaSource-Tests/SourceJavadocToXmldocParserTests.cs @@ -21,12 +21,18 @@ public class SourceJavadocToXmldocParserTests : SourceJavadocToXmldocGrammarFixt public void TryParse (ParseResult parseResult) { ParseTree parseTree; - var p = new SourceJavadocToXmldocParser (XmldocStyle.Full); + var p = new SourceJavadocToXmldocParser (new XmldocSettings { + Style = XmldocStyle.Full, + DocRootValue = DocRootPrefixActual, + }); var n = p.TryParse (parseResult.Javadoc, null, out parseTree); Assert.IsFalse (parseTree.HasErrors (), DumpMessages (parseTree, p)); Assert.AreEqual (parseResult.FullXml, GetMemberXml (n), $"while parsing input: ```{parseResult.Javadoc}```"); - p = new SourceJavadocToXmldocParser (XmldocStyle.IntelliSense); + p = new SourceJavadocToXmldocParser (new XmldocSettings { + Style = XmldocStyle.IntelliSense, + DocRootValue = DocRootPrefixActual, + }); n = p.TryParse (parseResult.Javadoc, null, out parseTree); Assert.IsFalse (parseTree.HasErrors (), DumpMessages (parseTree, p)); Assert.AreEqual (parseResult.IntelliSenseXml, GetMemberXml (n), $"while parsing input: ```{parseResult.Javadoc}```"); @@ -168,6 +174,28 @@ more description here. ", IntelliSenseXml = @" Summary. +", + }, + new ParseResult { + Javadoc = @"See accept(2). Insert +more description here. +How about another link accept(2) +@param manifest The value of the {@code +android:versionCode} manifest attribute. +", + FullXml = $@" + The value of the android:versionCode manifest attribute. + See accept(2). + + See accept(2). Insert +more description here. +How about another link accept(2) + +", + IntelliSenseXml = $@" + The value of the android:versionCode manifest attribute. + See accept(2). ", }, }; diff --git a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/JavadocInfo.cs b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/JavadocInfo.cs index 8ee02b159..99ab9ac5d 100644 --- a/tools/generator/Java.Interop.Tools.Generator.ObjectModel/JavadocInfo.cs +++ b/tools/generator/Java.Interop.Tools.Generator.ObjectModel/JavadocInfo.cs @@ -25,6 +25,7 @@ public sealed class JavadocInfo { public XElement[] Copyright { get; set; } public XmldocStyle XmldocStyle { get; set; } + public string DocRootReplacement { get; set; } string MemberDescription; @@ -44,6 +45,7 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool var extras = GetExtra (element, style, declaringJniType, declaringMemberName, declaringMemberParamString, appendCopyrightExtra); XElement[] extra = extras.Extras; XElement[] copyright = extras.Copyright; + string docRoot = extras.DocRoot; if (string.IsNullOrEmpty (javadoc) && extra == null) return null; @@ -51,6 +53,7 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool var info = new JavadocInfo () { ExtraRemarks = extra, Copyright = copyright, + DocRootReplacement = docRoot, Javadoc = javadoc, MemberDescription = declaringMemberName == null ? declaringJniType @@ -95,10 +98,10 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool return (declaringJniType, declaringMemberName, declaringMemberParameterString); } - static (XElement[] Extras, XElement[] Copyright) GetExtra (XElement element, XmldocStyle style, string declaringJniType, string declaringMemberName, string declaringMemberParameterString, bool appendCopyrightExtra) + static (XElement[] Extras, XElement[] Copyright, string DocRoot) GetExtra (XElement element, XmldocStyle style, string declaringJniType, string declaringMemberName, string declaringMemberParameterString, bool appendCopyrightExtra) { if (!style.HasFlag (XmldocStyle.IntelliSenseAndExtraRemarks)) - return (null, null); + return (null, null, null); XElement javadocMetadata = null; while (element != null) { @@ -111,10 +114,12 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool List extra = null; IEnumerable copyright = null; + string docRoot = null; if (javadocMetadata != null) { var link = javadocMetadata.Element ("link"); var urlPrefix = (string) link.Attribute ("prefix"); var linkStyle = (string) link.Attribute ("style"); + docRoot = (string) link.Attribute ("docroot"); var kind = ParseApiLinkStyle (linkStyle); XElement docLink = null; @@ -128,7 +133,7 @@ public static JavadocInfo CreateInfo (XElement element, XmldocStyle style, bool extra.AddRange (copyright); } } - return (extra?.ToArray (), copyright?.ToArray ()); + return (extra?.ToArray (), copyright?.ToArray (), docRoot); } static ApiLinkStyle ParseApiLinkStyle (string style) @@ -159,9 +164,11 @@ public IEnumerable ParseJavadoc () IEnumerable nodes = null; try { - var parser = new SourceJavadocToXmldocParser (XmldocStyle) { - ExtraRemarks = ExtraRemarks, - }; + var parser = new SourceJavadocToXmldocParser (new XmldocSettings { + Style = XmldocStyle, + ExtraRemarks = ExtraRemarks, + DocRootValue = DocRootReplacement, + }); nodes = parser.TryParse (Javadoc, fileName: null, out tree); } catch (Exception e) { diff --git a/tools/java-source-utils/src/main/java/com/microsoft/android/App.java b/tools/java-source-utils/src/main/java/com/microsoft/android/App.java index f8f449feb..9b15ef730 100644 --- a/tools/java-source-utils/src/main/java/com/microsoft/android/App.java +++ b/tools/java-source-utils/src/main/java/com/microsoft/android/App.java @@ -68,7 +68,7 @@ static void generateParamsTxt(String filename, JniPackagesInfo packages) throws static void generateXml(JavaSourceUtilsOptions options, JniPackagesInfo packages) throws Throwable { try (final JavadocXmlGenerator javadocXmlGen = new JavadocXmlGenerator(options.outputJavadocXml)) { - javadocXmlGen.writeCopyrightInfo(options.docCopyrightFile, options.docUrlPrefix, options.docUrlStyle); + javadocXmlGen.writeCopyrightInfo(options.docCopyrightFile, options.docUrlPrefix, options.docUrlStyle, options.docRootUrl); javadocXmlGen.writePackages(packages); } } diff --git a/tools/java-source-utils/src/main/java/com/microsoft/android/JavaSourceUtilsOptions.java b/tools/java-source-utils/src/main/java/com/microsoft/android/JavaSourceUtilsOptions.java index 448101f07..13a3520ff 100644 --- a/tools/java-source-utils/src/main/java/com/microsoft/android/JavaSourceUtilsOptions.java +++ b/tools/java-source-utils/src/main/java/com/microsoft/android/JavaSourceUtilsOptions.java @@ -74,6 +74,8 @@ public class JavaSourceUtilsOptions implements AutoCloseable { " Stored in //javadoc-metadata/link/@style.\n" + " Supported styles include:\n" + " - developer.android.com/reference@2020-Nov\n" + + " --doc-root-url URL Base URL to use in place of @{docRoot} elements.\n" + + " Stored in //javadoc-metadata/link/@docroot.\n" + "\n" + "Output file options:\n" + " -P, --output-params FILE Write method parameter names to FILE.\n" + @@ -94,6 +96,7 @@ public class JavaSourceUtilsOptions implements AutoCloseable { public File docCopyrightFile; public String docUrlPrefix; public String docUrlStyle; + public String docRootUrl; private final Collection sourceDirectoryFiles = new ArrayList(); private File extractedTempDir; @@ -204,6 +207,11 @@ private final JavaSourceUtilsOptions parse(Iterator args) throws IOExcep docUrlStyle = style; break; } + case "--doc-root-url": { + final String docRoot = getNextOptionValue(args, arg); + docRootUrl = docRoot; + break; + } case "-j": case "--jar": { final File file = getNextOptionFile(args, arg); diff --git a/tools/java-source-utils/src/main/java/com/microsoft/android/JavadocXmlGenerator.java b/tools/java-source-utils/src/main/java/com/microsoft/android/JavadocXmlGenerator.java index 690474b03..7b379c99e 100644 --- a/tools/java-source-utils/src/main/java/com/microsoft/android/JavadocXmlGenerator.java +++ b/tools/java-source-utils/src/main/java/com/microsoft/android/JavadocXmlGenerator.java @@ -80,7 +80,7 @@ public void close() throws TransformerException { } } - public final void writeCopyrightInfo(final File copyright, final String urlPrefix, final String urlStyle) throws IOException, ParserConfigurationException { + public final void writeCopyrightInfo(final File copyright, final String urlPrefix, final String urlStyle, final String docRootUrl) throws IOException, ParserConfigurationException { final Element info = document.createElement("javadoc-metadata"); if (copyright != null) { final Element blurb = document.createElement("copyright"); @@ -95,13 +95,19 @@ public final void writeCopyrightInfo(final File copyright, final String urlPrefi } info.appendChild(blurb); } - if (urlPrefix != null && urlStyle != null) { - final Element link = document.createElement("link"); + + final Element link = document.createElement("link"); + if (urlPrefix != null) { link.setAttribute("prefix", urlPrefix); + } + if (urlStyle != null) { link.setAttribute("style", urlStyle); - - info.appendChild(link); } + if (docRootUrl != null) { + link.setAttribute("docroot", docRootUrl); + } + + info.appendChild(link); if (info.hasChildNodes()) { api.appendChild(info);