diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 7e77a3a2..23995348 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,46 +1,18 @@ # Contributor Covenant Code of Conduct -## Our Pledge +This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +In particular this means: -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +> We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments * Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +* Publishing other's private information, such as physical or electronic addresses, without explicit permission +* Other unethical or unprofessional conduct -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ +For the complete set of rules and more information on the topic see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c0a5c0ff..f0a00c4f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,11 +6,11 @@ The AngleSharp project ultimately tries to provide tools to parse, inspect, modi ## Code License -This is an open source project falling under the MIT License. By using, distributing, or contributing to this project, you accept and agree that all code within the AngleSharp project are licensed under MIT license. +This is an open source project falling under the [MIT License](../LICENSE). By using, distributing, or contributing to this project, you accept and agree that all code within the AngleSharp project and its libraries are licensed under MIT license. ## Becoming a Contributor -Until the project has enough contributors a BDFL model is followed. As such the sole key maintainer keeps the right to appoint GitHub members as regular project contributors. Nevertheless, usually appointing someone follows this process: +Until the project has enough contributors a [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) model is followed. As such the sole key maintainer keeps the right to appoint GitHub members as regular project contributors. Nevertheless, usually appointing someone follows this process: 1. An individual contributes actively via discussions (reporting bugs, giving feedback to existing or opening new issues) and / or pull requests 2. The individual is either directly asked, invited or asks for contributor rights on the project @@ -22,11 +22,17 @@ Every contributor has to sign the contributor's license agreement (CLA) to estab ### Issue Discussion -Discussion of issues should be placed transparently in the [issue tracker](https://github.com/FlorianRappl/AngleSharp/issues/) here on GitHub. +Discussion of issues should be placed transparently in the issue tracker here on GitHub. + +* [AngleSharp.Core](https://github.com/AngleSharp/AngleSharp/issues/) +* [AngleSharp.Css](https://github.com/AngleSharp/AngleSharp.Css/issues/) +* [AngleSharp.Io](https://github.com/AngleSharp/AngleSharp.Io/issues/) +* [AngleSharp.Js](https://github.com/AngleSharp/AngleSharp.Js/issues/) +* [AngleSharp.Xml](https://github.com/AngleSharp/AngleSharp.Xml/issues/) ### Modifying the code -AngleSharp uses features from C# 6 and will soon switch to C# 7. You will therefore need a C# compiler that is up for the job. +AngleSharp and its libraries uses features from the latest versions of C# (e.g., C# 7). You will therefore need a C# compiler that is up for the job. 1. Fork and clone the repo. 2. First try to build the AngleSharp.Core libray and see if you get the tests running. @@ -34,7 +40,7 @@ AngleSharp uses features from C# 6 and will soon switch to C# 7. You will theref AngleSharp itself does not have dependencies, however, the tests are dependent on NUnit. -The build system of AngleSharp uses Cake. A bootstrap script (build.ps1 for Windows or build.sh for *nix systems) is included. Note, that at the moment AngleSharp requires NuGet 3.5, which looks for MSBuild pre-15, i.e., before Visual Studio 2017 on Windows systems. +The build system of AngleSharp uses Cake. A bootstrap script (build.ps1 for Windows or build.sh for *nix systems) is included. Note, that at the moment AngleSharp may require NuGet 3.5, which looks for MSBuild pre-15, i.e., before Visual Studio 2017 on Windows systems. We aim to drop this requirement enitirely soon. ### Code Conventions @@ -49,12 +55,13 @@ There are a couple of rules, which are definitely not standard, but highly recom - AngleSharp uses the RHS convention, where types are always put on the right hand side if possible, i.e., preferring `var` under all circumstances - A single empty line between two non-simple statements (e.g., for-loop and if-condition) should be inserted - Types are preferred to keywords (`String` instead of `string` or `Int32` instead of `int`) +- `using` statements must be inside the namespace declaration ### Development Workflow 1. If no issue already exists for the work you'll be doing, create one to document the problem(s) being solved and self-assign. 2. Otherwise please let us know that you are working on the problem. Regular status updates (e.g. "still in progress", "no time anymore", "practically done", "pull request issued") are highly welcome. -2. Create a new branch—please don't work in the `master` branch directly. It is reserved for releases. We recommend naming the branch to match the issue being addressed (`feature-#777` or `issue-777`). +2. Create a new branch—please don't work in the `master` branch directly. It is reserved for releases. We recommend naming the branch to match the issue being addressed (`feature/#777` or `issue-777`). 3. Add failing tests for the change you want to make. Tests are crucial and should be taken from W3C (or other specification). 4. Fix stuff. Always go from edge case to edge case. 5. All tests should pass now. Also your new implementation should not break existing tests. @@ -63,46 +70,64 @@ There are a couple of rules, which are definitely not standard, but highly recom Just to illustrate the git workflow for AngleSharp a little bit more we've added the following graphs. -Initially AngleSharp starts at the `master` branch. This branch should contain the latest stable (or released) version. - -![AngleSharp Initial Master](https://github.com/AngleSharp/AngleSharp/wiki/initial-master.png) +Initially, AngleSharp starts at the `master` branch. This branch should contain the latest stable (or released) version. Here we now created a new branch called `devel`. This is the development branch. -![AngleSharp Initial Devel](https://github.com/AngleSharp/AngleSharp/wiki/initial-devel.png) - Now active work is supposed to be done. Therefore a new branch should be created. Let's create one: - git checkout -b feature-#777 +``` +git checkout -b feature/#777 +``` There may be many of these feature branches. Most of them are also pushed to the server for discussion or synchronization. - git push -u origin feature-#777 +``` +git push -u origin feature/#777 +``` -At this point the graph could look as follows. The diagram shows two feature branches in different stages. +Now feature branches may be closed when they are done. Here we simply merge with the feature branch(es). For instance the following command takes the `feature/#777` branch from the server and merges it with the `devel` branch. -![AngleSharp Feature Branches](https://github.com/AngleSharp/AngleSharp/wiki/feature-branches.png) +``` +git checkout devel +git pull +git pull origin feature/#777 +git push +``` -Now feature branches may be closed when they are done. Here we simply merge with the feature branch(es). For instance the following command takes the `feature-#777` branch from the server and merges it with the `devel` branch. +Finally, we may have all the features that are needed to release a new version of AngleSharp. Here we tag the release. For instance for the 1.0 release we use `v1.0`. - git checkout devel - git pull - git pull origin feature-#777 - git push +``` +git checkout master +git merge devel +git tag v1.0 +``` -This aggregates to the graph below. +(The last part is automatically performed by our CI system.) -![AngleSharp Merge Branches](https://github.com/AngleSharp/AngleSharp/wiki/feature-merges.png) +### Basic Files -Finally we may have all the features that are needed to release a new version of AngleSharp. Here we tag the release. For instance for the 1.0 release we use `v1.0`. +The following files should not be edited directly in the current repository, but rather in the `AngleSharp.GitBase` repository. They are then synced via `git pull` from a different remote. - git checkout master - git merge devel - git tag v1.0 +``` +.editorconfig +.gitignore +.gitattributes +.github/* +appveyor.yml +build.ps1 +build.sh +tools/anglesharp.cake +tools/packages.config +LICENSE +``` -Hence finally we have the full graph. +To sync manually: -![AngleSharp Release Git Graph](https://github.com/AngleSharp/AngleSharp/wiki/release.png) +``` +git remote add gitbase git@github.com:AngleSharp/AngleSharp.GitBase.git +git pull gitbase master +``` ### Versioning diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 6ab7bf99..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,6 +0,0 @@ -# Issue Templates - -Please use one of the following templates to create your issue: - -- [BUG REPORT](https://github.com/AngleSharp/AngleSharp.Css/issues/new?template=bugs.md) -- [FEATURE PROPOSAL](https://github.com/AngleSharp/AngleSharp.Css/issues/new?template=features.md) diff --git a/.github/ISSUE_TEMPLATE/bugs.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 64% rename from .github/ISSUE_TEMPLATE/bugs.md rename to .github/ISSUE_TEMPLATE/bug_report.md index ab032964..44871a98 100644 --- a/.github/ISSUE_TEMPLATE/bugs.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,20 +1,28 @@ -## Bug Report +--- +name: Bug Report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' +--- -### Prerequisites +# Bug Report + +## Prerequisites - [ ] Can you reproduce the problem in a [MWE](https://en.wikipedia.org/wiki/Minimal_working_example)? - [ ] Are you running the latest version of AngleSharp? - [ ] Did you check the FAQs to see if that helps you? -- [ ] Are you reporting to the correct repository? (if its an issue with the core library, please report to `AngleSharp` directly) +- [ ] Are you reporting to the correct repository? (there are multiple AngleSharp libraries, e.g., `AngleSharp.Css` for CSS support) - [ ] Did you perform a search in the issues? For more information, see the `CONTRIBUTING` guide. -### Description +## Description [Description of the bug] -### Steps to Reproduce +## Steps to Reproduce 1. [First Step] 2. [Second Step] @@ -26,6 +34,6 @@ For more information, see the `CONTRIBUTING` guide. **Environment details:** [OS, .NET Runtime, ...] -### Possible Solution +## Possible Solution [Optionally, share your idea to fix the issue] diff --git a/.github/ISSUE_TEMPLATE/features.md b/.github/ISSUE_TEMPLATE/feature_request.md similarity index 53% rename from .github/ISSUE_TEMPLATE/features.md rename to .github/ISSUE_TEMPLATE/feature_request.md index 29cea72b..7722f052 100644 --- a/.github/ISSUE_TEMPLATE/features.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,13 +1,21 @@ -## New Feature Proposal +--- +name: Feature Request +about: Suggest an idea for this project +title: '' +labels: 'enhancement' +assignees: '' +--- -### Description +# New Feature Proposal + +## Description [Description of the proposed feature] -### Background +## Background Provide any additional background for the feature. e.g., user scenarios. -### Specification +## Specification In case of updates that adhere to specification changes, please reference the used specification. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b20f514a..f3f3c419 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,13 +1,13 @@ -## Types of Changes +# Types of Changes -### Prerequisites +## Prerequisites Please make sure you can check the following two boxes: - [ ] I have read the **CONTRIBUTING** document - [ ] My code follows the code style of this project -### Contribution Type +## Contribution Type What types of changes does your code introduce? Put an `x` in all the boxes that apply: @@ -19,6 +19,6 @@ What types of changes does your code introduce? Put an `x` in all the boxes that - [ ] I have added tests to cover my changes - [ ] All new and existing tests passed -### Description +## Description [Place a meaningful description here.] diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc1a0d5..63d34fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 0.13.0 + +Released on Friday, September 6 2019. + +- Added `Compute` extension for `ICssStyleDeclaration` +- Added `GetMatchingStyles` extension for `ICssRuleList` +- Added `MinifyStyleFormatter` +- Added `Prettify` and `Minify` extension methods +- Fixed border-style expansion order (#34) +- Fixed text-decoration expansion order (#35) +- Fixed missing whitespace in `GetInnerText()` (#32) + # 0.12.1 Released on Wednesday, May 15 2019. diff --git a/LICENSE b/LICENSE index f6235f1b..c2b5b2bb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 - 2019 AngleSharp +Copyright (c) 2013 - 2019 AngleSharp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..6e6985f6 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,20 @@ +version: '{build}' +branches: + only: + - master + - devel +skip_tags: true +image: Visual Studio 2015 +configuration: Release +platform: Any CPU +build_script: +- ps: >- + if ($env:APPVEYOR_PULL_REQUEST_NUMBER -eq $null -and $env:APPVEYOR_REPO_BRANCH -eq "master") { + .\build.ps1 -Target Publish + } elseif ($env:APPVEYOR_PULL_REQUEST_NUMBER -eq $null -and $env:APPVEYOR_REPO_BRANCH -eq "devel") { + .\build.ps1 -Target PrePublish + } else { + .\build.ps1 -Target AppVeyor + } +test: off +deploy: off diff --git a/build.cake b/build.cake index 51fa445e..f1995e0b 100644 --- a/build.cake +++ b/build.cake @@ -1,220 +1,13 @@ -/* **************************************** - Publishing workflow - ------------------- - - - Update CHANGELOG.md - - Run a normal build with Cake - - Push to devel and FF merge to master - - Switch to master - - Run a Publish build with Cake - - Switch back to devel branch - **************************************** */ - -#addin "Cake.FileHelpers" -#addin "Octokit" -using Octokit; - var target = Argument("target", "Default"); -var configuration = Argument("configuration", "Release"); -var isRunningOnUnix = IsRunningOnUnix(); -var isRunningOnWindows = IsRunningOnWindows(); -var isRunningOnAppVeyor = AppVeyor.IsRunningOnAppVeyor; -var isPullRequest = AppVeyor.Environment.PullRequest.IsPullRequest; -var buildNumber = AppVeyor.Environment.Build.Number; -var releaseNotes = ParseReleaseNotes("./CHANGELOG.md"); -var version = releaseNotes.Version.ToString(); -var buildDir = Directory("./src/AngleSharp.Css/bin") + Directory(configuration); -var buildResultDir = Directory("./bin") + Directory(version); -var nugetRoot = buildResultDir + Directory("nuget"); - -// Initialization -// ---------------------------------------- - -Setup(_ => +var projectName = "AngleSharp.Css"; +var solutionName = "AngleSharp.Css"; +var frameworks = new Dictionary { - Information("Building version {0} of AngleSharp.Css.", version); - Information("For the publish target the following environment variables need to be set:"); - Information(" NUGET_API_KEY, GITHUB_API_TOKEN"); -}); - -// Tasks -// ---------------------------------------- - -Task("Clean") - .Does(() => - { - CleanDirectories(new DirectoryPath[] { buildDir, buildResultDir, nugetRoot }); - }); - -Task("Restore-Packages") - .IsDependentOn("Clean") - .Does(() => - { - NuGetRestore("./src/AngleSharp.Css.sln", new NuGetRestoreSettings - { - ToolPath = "tools/nuget.exe", - }); - }); - -Task("Build") - .IsDependentOn("Restore-Packages") - .Does(() => - { - ReplaceRegexInFiles("./src/Directory.Build.props", "(?<=)(.+?)(?=)", version); - DotNetCoreBuild("./src/AngleSharp.Css.sln", new DotNetCoreBuildSettings - { - Configuration = configuration, - }); - }); - -Task("Run-Unit-Tests") - .IsDependentOn("Build") - .Does(() => - { - var settings = new DotNetCoreTestSettings - { - Configuration = configuration, - }; - - if (isRunningOnAppVeyor) - { - settings.TestAdapterPath = Directory("."); - settings.Logger = "Appveyor"; - // TODO Finds a way to exclude tests not allowed to run on appveyor - // Not used in current code - //settings.Where = "cat != ExcludeFromAppVeyor"; - } - - DotNetCoreTest("./src/AngleSharp.Css.Tests/", settings); - }); - -Task("Copy-Files") - .IsDependentOn("Build") - .Does(() => - { - var mapping = new Dictionary - { - { "net46", "net46" }, - { "netstandard2.0", "netstandard2.0" }, - }; - - if (!isRunningOnWindows) - { - mapping.Remove("net46"); - } - - foreach (var item in mapping) - { - var targetDir = nugetRoot + Directory("lib") + Directory(item.Key); - CreateDirectory(targetDir); - CopyFiles(new FilePath[] - { - buildDir + Directory(item.Value) + File("AngleSharp.Css.dll"), - buildDir + Directory(item.Value) + File("AngleSharp.Css.xml"), - }, targetDir); - } - - CopyFiles(new FilePath[] { "src/AngleSharp.Css.nuspec" }, nugetRoot); - }); - -Task("Create-Package") - .IsDependentOn("Copy-Files") - .Does(() => - { - var nugetExe = GetFiles("./tools/**/nuget.exe").FirstOrDefault() - ?? (isRunningOnAppVeyor ? GetFiles("C:\\Tools\\NuGet3\\nuget.exe").FirstOrDefault() : null) - ?? throw new InvalidOperationException("Could not find nuget.exe."); - - var nuspec = nugetRoot + File("AngleSharp.Css.nuspec"); - - NuGetPack(nuspec, new NuGetPackSettings - { - Version = version, - OutputDirectory = nugetRoot, - Symbols = false, - Properties = new Dictionary - { - { "Configuration", configuration }, - }, - }); - }); - -Task("Publish-Package") - .IsDependentOn("Create-Package") - .IsDependentOn("Run-Unit-Tests") - .Does(() => - { - var apiKey = EnvironmentVariable("NUGET_API_KEY"); - - if (String.IsNullOrEmpty(apiKey)) - { - throw new InvalidOperationException("Could not resolve the NuGet API key."); - } - - foreach (var nupkg in GetFiles(nugetRoot.Path.FullPath + "/*.nupkg")) - { - NuGetPush(nupkg, new NuGetPushSettings - { - Source = "https://nuget.org/api/v2/package", - ApiKey = apiKey, - }); - } - }); - -Task("Publish-Release") - .IsDependentOn("Create-Package") - .IsDependentOn("Run-Unit-Tests") - .Does(() => - { - var githubToken = EnvironmentVariable("GITHUB_API_TOKEN"); - - if (String.IsNullOrEmpty(githubToken)) - { - throw new InvalidOperationException("Could not resolve AngleSharp GitHub token."); - } - - var github = new GitHubClient(new ProductHeaderValue("AngleSharpCakeBuild")) - { - Credentials = new Credentials(githubToken), - }; - - var newRelease = github.Repository.Release; - newRelease.Create("AngleSharp", "AngleSharp.Css", new NewRelease("v" + version) - { - Name = version, - Body = String.Join(Environment.NewLine, releaseNotes.Notes), - Prerelease = false, - TargetCommitish = "master" - }).Wait(); - }); - -Task("Update-AppVeyor-Build-Number") - .WithCriteria(() => isRunningOnAppVeyor) - .Does(() => - { - var num = AppVeyor.Environment.Build.Number; - AppVeyor.UpdateBuildVersion($"{version}-{num}"); - }); - -// Targets -// ---------------------------------------- - -Task("Package") - .IsDependentOn("Run-Unit-Tests") - .IsDependentOn("Create-Package"); - -Task("Default") - .IsDependentOn("Package"); - -Task("Publish") - .IsDependentOn("Publish-Package") - .IsDependentOn("Publish-Release"); - -Task("AppVeyor") - .IsDependentOn("Run-Unit-Tests") - .IsDependentOn("Update-AppVeyor-Build-Number"); + { "net46", "net46" }, + { "netstandard1.3", "netstandard1.3" }, + { "netstandard2.0", "netstandard2.0" }, +}; -// Execution -// ---------------------------------------- +#load tools/anglesharp.cake -RunTarget(target); \ No newline at end of file +RunTarget(target); diff --git a/doc/Values.md b/doc/Values.md new file mode 100644 index 00000000..6220c261 --- /dev/null +++ b/doc/Values.md @@ -0,0 +1,8 @@ +# Types of Values + +For details see [CSS2 specification](https://www.w3.org/TR/CSS2/cascade.html#value-stages). + +* Specified values +* Computed values +* Used values +* Actual values diff --git a/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj b/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj index 8e3df294..09b9274f 100644 --- a/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj +++ b/src/AngleSharp.Css.Tests/AngleSharp.Css.Tests.csproj @@ -4,6 +4,7 @@ true Key.snk false + 7.1 @@ -13,7 +14,7 @@ - + diff --git a/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs b/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs index 264651ea..b444da1d 100644 --- a/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs +++ b/src/AngleSharp.Css.Tests/CssConstructionFunctions.cs @@ -9,14 +9,7 @@ namespace AngleSharp.Css.Tests static class CssConstructionFunctions { - internal static IHtmlDocument ParseDocument(String source) - { - var context = BrowsingContext.New(Configuration.Default.WithCss()); - var parser = context.GetService(); - return parser.ParseDocument(source); - } - - internal static IHtmlDocument ParseDocument(String source, CssParserOptions options) + internal static IHtmlDocument ParseDocument(String source, CssParserOptions options = default) { var context = BrowsingContext.New(Configuration.Default.WithCss(options)); var parser = context.GetService(); @@ -35,19 +28,13 @@ internal static IFeatureValidator CreateMediaFeatureValidator(String name) return factory.Create(name); } - internal static CssStyleSheet ParseStyleSheet(String source) + internal static CssStyleSheet ParseStyleSheet(Stream source, CssParserOptions options = default) { - var parser = new CssParser(); - return parser.ParseStyleSheet(source) as CssStyleSheet; - } - - internal static CssStyleSheet ParseStyleSheet(Stream source) - { - var parser = new CssParser(); + var parser = new CssParser(options); return parser.ParseStyleSheet(source) as CssStyleSheet; } - internal static CssStyleSheet ParseStyleSheet(String source, CssParserOptions options) + internal static CssStyleSheet ParseStyleSheet(String source, CssParserOptions options = default) { var parser = new CssParser(options); return parser.ParseStyleSheet(source) as CssStyleSheet; diff --git a/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs index d54b931c..98f667e5 100644 --- a/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs +++ b/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs @@ -3,6 +3,7 @@ namespace AngleSharp.Css.Tests.Declarations { + using AngleSharp.Dom; using NUnit.Framework; using static CssConstructionFunctions; @@ -244,6 +245,22 @@ public void CssBorderStyleHiddenDottedNoneNoneLegal() Assert.AreEqual("hidden dotted none none", property.Value); } + [Test] + public void CssBorderStyleMultipleExpandCorrectly_Issue34() + { + var source = @" + + + +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var styleDeclaration = document.Body.ComputeCurrentStyle(); + Assert.AreEqual("hidden", styleDeclaration.GetBorderTopStyle()); + Assert.AreEqual("double", styleDeclaration.GetBorderLeftStyle()); + Assert.AreEqual("double", styleDeclaration.GetBorderRightStyle()); + Assert.AreEqual("dashed", styleDeclaration.GetBorderBottomStyle()); + } + [Test] public void CssBorderStyleWavyIllegal() { diff --git a/src/AngleSharp.Css.Tests/Declarations/CssTextProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssTextProperty.cs index 381bb5c3..bf455dc7 100644 --- a/src/AngleSharp.Css.Tests/Declarations/CssTextProperty.cs +++ b/src/AngleSharp.Css.Tests/Declarations/CssTextProperty.cs @@ -1,5 +1,7 @@ namespace AngleSharp.Css.Tests.Declarations { + using AngleSharp.Css.Dom; + using AngleSharp.Dom; using NUnit.Framework; using static CssConstructionFunctions; @@ -195,6 +197,20 @@ public void CssTextDecorationLegalLineThrough() Assert.AreEqual("line-through", property.Value); } + [Test] + public void CssTextDecorationExpandCorrectly_Issue35() + { + var source = @" + + + +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var styleDeclaration = document.Body.ComputeCurrentStyle(); + Assert.AreEqual("dotted", styleDeclaration.GetTextDecorationStyle()); + Assert.AreEqual("underline", styleDeclaration.GetTextDecorationLine()); + } + [Test] public void CssTextDecorationLegalUnderlineOverline() { diff --git a/src/AngleSharp.Css.Tests/Extensions/InnerText.cs b/src/AngleSharp.Css.Tests/Extensions/InnerText.cs index c8fe56b4..7003ff57 100644 --- a/src/AngleSharp.Css.Tests/Extensions/InnerText.cs +++ b/src/AngleSharp.Css.Tests/Extensions/InnerText.cs @@ -80,5 +80,15 @@ public void GetInnerText(String fixture, String expected) Assert.AreEqual(expected, doc.Body.GetInnerText()); } + + [Test] + public void SpanShouldNotHaveRemovedSpaces_Issue32() + { + var config = Configuration.Default.WithCss(); + var document = ("
Div with a span in it.
").ToHtmlDocument(config); + var element = document.QuerySelector("div"); + + Assert.AreEqual("Div with a span in it.", element.GetInnerText()); + } } } diff --git a/src/AngleSharp.Css.Tests/Mocks/PageRequester.cs b/src/AngleSharp.Css.Tests/Mocks/PageRequester.cs index b0142801..29bc34c2 100644 --- a/src/AngleSharp.Css.Tests/Mocks/PageRequester.cs +++ b/src/AngleSharp.Css.Tests/Mocks/PageRequester.cs @@ -1,6 +1,7 @@ namespace AngleSharp.Css.Tests.Mocks { using AngleSharp.Io; + using AngleSharp.Text; using System; using System.IO; using System.Net; @@ -11,9 +12,22 @@ namespace AngleSharp.Css.Tests.Mocks sealed class PageRequester : BaseRequester { private readonly static DefaultHttpRequester _default = new DefaultHttpRequester(); - private readonly static String _directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "..", "..", "..", "Resources"); + private readonly static String _directory = GetResourceDirectory(); private readonly static SiteMapping _mapping = new SiteMapping(Path.Combine(_directory, "content.xml")); + private static String GetResourceDirectory() + { + var baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var path = new DirectoryInfo(baseDir); + + while (!path.Name.Is("AngleSharp.Css.Tests")) + { + path = path.Parent; + } + + return Path.Combine(path.FullName, "Resources"); + } + public override Boolean SupportsProtocol(String protocol) { return _default.SupportsProtocol(protocol); diff --git a/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs b/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs index ae52a27e..9af111fd 100644 --- a/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs +++ b/src/AngleSharp.Css.Tests/Styling/BasicStyling.cs @@ -154,5 +154,61 @@ public void CssStyleDeclarationBoundInboundDirection() Assert.AreEqual("background-color: rgb(255, 0, 0); color: rgb(0, 0, 0)", element.GetAttribute("style")); Assert.AreEqual(2, element.GetStyle().Length); } + + [Test] + public void MinifyRemovesComment() + { + var sheet = ParseStyleSheet("h1 /* this is a comment */ { color: red; /*another comment*/ }"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("h1{color:rgba(255, 0, 0, 1)}", result); + } + + [Test] + public void MinifyRemovesEmptyStyleRule() + { + var sheet = ParseStyleSheet("h1 { }"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("", result); + } + + [Test] + public void MinifyRemovesEmptyStyleRuleKeepsOtherRule() + { + var sheet = ParseStyleSheet("h1 { } h2 { font-size:0; }"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("h2{font-size:0}", result); + } + + [Test] + public void MinifyRemovesEmptyMediaRule() + { + var sheet = ParseStyleSheet("@media screen { h1 { } }"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("", result); + } + + [Test] + public void MinifyDoesNotRemovesMediaRuleIfOneStyleIsInThere() + { + var sheet = ParseStyleSheet("@media screen { h1 { } h2 { top:0} }"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("@media screen{h2{top:0}}", result); + } + + [Test] + public void MinifyWorksWithNestedMediaRules() + { + var sheet = ParseStyleSheet("@media screen { @media screen{h1{}}div{border-top : none} }"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("@media screen{div{border-top:none}}", result); + } + + [Test] + public void MinifyWithMultipleDeclarations() + { + var sheet = ParseStyleSheet("h1 { top:0 ; left: 2px; border: none; } h2 { border: 1px solid red;} h3{}"); + var result = sheet.ToCss(new MinifyStyleFormatter()); + Assert.AreEqual("h1{top:0;left:2px;border:none}h2{border:1px solid rgba(255, 0, 0, 1)}", result); + } } } diff --git a/src/AngleSharp.Css.nuspec b/src/AngleSharp.Css.nuspec index 4b9fa451..25b04819 100644 --- a/src/AngleSharp.Css.nuspec +++ b/src/AngleSharp.Css.nuspec @@ -14,7 +14,15 @@ Copyright 2016-2019, AngleSharp html html5 css css3 dom styling library anglesharp angle - + + + + + + + + + diff --git a/src/AngleSharp.Css/AngleSharp.Css.csproj b/src/AngleSharp.Css/AngleSharp.Css.csproj index 378be467..97287493 100644 --- a/src/AngleSharp.Css/AngleSharp.Css.csproj +++ b/src/AngleSharp.Css/AngleSharp.Css.csproj @@ -2,15 +2,21 @@ AngleSharp.Css AngleSharp.Css - netstandard2.0 - netstandard2.0;net46 + netstandard1.3;netstandard2.0 + netstandard1.3;netstandard2.0;net46 true Key.snk true + 7.1 - + + + + + + diff --git a/src/AngleSharp.Css/Constants/CssKeywords.cs b/src/AngleSharp.Css/Constants/CssKeywords.cs index 404b0488..bced4207 100644 --- a/src/AngleSharp.Css/Constants/CssKeywords.cs +++ b/src/AngleSharp.Css/Constants/CssKeywords.cs @@ -232,6 +232,11 @@ public static class CssKeywords /// public static readonly String Right = "right"; + /// + /// The footnote keyword. + /// + public static readonly String Footnote = "footnote"; + /// /// The both keyword. /// @@ -378,7 +383,17 @@ public static class CssKeywords public static readonly String Stretch = "stretch"; /// - /// The stretch keyword. + /// The compact keyword. + /// + public static readonly String Compact = "compact"; + + /// + /// The line keyword. + /// + public static readonly String Line = "line"; + + /// + /// The inline keyword. /// public static readonly String Inline = "inline"; @@ -1531,5 +1546,15 @@ public static class CssKeywords /// The visiblePainted keyword. /// public static readonly String VisiblePainted = "visiblePainted"; + + /// + /// The open keyword. + /// + public static readonly String Open = "open"; + + /// + /// The closed keyword. + /// + public static readonly String Closed = "closed"; } } diff --git a/src/AngleSharp.Css/Constants/FunctionNames.cs b/src/AngleSharp.Css/Constants/FunctionNames.cs index b365c948..af8b1547 100644 --- a/src/AngleSharp.Css/Constants/FunctionNames.cs +++ b/src/AngleSharp.Css/Constants/FunctionNames.cs @@ -251,5 +251,15 @@ public static class FunctionNames /// The hwba function. /// public static readonly String Hwba = "hwba"; + + /// + /// The content function. + /// + public static readonly String Content = "content"; + + /// + /// The running function. + /// + public static readonly String Running = "running"; } } diff --git a/src/AngleSharp.Css/Constants/InitialValues.cs b/src/AngleSharp.Css/Constants/InitialValues.cs index 48aaf51f..9421bfb0 100644 --- a/src/AngleSharp.Css/Constants/InitialValues.cs +++ b/src/AngleSharp.Css/Constants/InitialValues.cs @@ -23,6 +23,13 @@ static class InitialValues public static readonly ICssValue BackgroundOriginDecl = new Constant(CssKeywords.BorderBox, BoxModel.PaddingBox); public static readonly ICssValue BackgroundClipDecl = new Constant(CssKeywords.BorderBox, BoxModel.BorderBox); public static readonly ICssValue BackgroundAttachmentDecl = new Constant(CssKeywords.Scroll, BackgroundAttachment.Scroll); + public static readonly ICssValue BookmarkStateDecl = new Constant(CssKeywords.Open, BookmarkState.Open); + public static readonly ICssValue BookmarkLabelDecl = new Constant(CssKeywords.None, null); + public static readonly ICssValue BookmarkLevelDecl = new Constant(CssKeywords.None, 0); + public static readonly ICssValue FootnotePolicyDecl = new Constant(CssKeywords.Auto, FootnotePolicy.Auto); + public static readonly ICssValue FootnoteDisplayDecl = new Constant(CssKeywords.Block, FootnoteDisplay.Block); + public static readonly ICssValue RunningDecl = new Identifier(CssKeywords.None); + public static readonly ICssValue StringSetDecl = new Constant(CssKeywords.None, null); public static readonly ICssValue FontStyleDecl = new Constant(CssKeywords.Normal, FontStyle.Normal); public static readonly ICssValue FontVariantDecl = new Constant(CssKeywords.Normal, FontVariant.Normal); public static readonly ICssValue FontWeightDecl = new Constant(CssKeywords.Normal, FontWeight.Normal); diff --git a/src/AngleSharp.Css/Constants/Map.cs b/src/AngleSharp.Css/Constants/Map.cs index e9b625e4..7a5b7ccf 100644 --- a/src/AngleSharp.Css/Constants/Map.cs +++ b/src/AngleSharp.Css/Constants/Map.cs @@ -522,6 +522,7 @@ static class Map { CssKeywords.None, Floating.None }, { CssKeywords.Left, Floating.Left }, { CssKeywords.Right, Floating.Right }, + { CssKeywords.Footnote, Floating.Footnote }, }; /// @@ -840,5 +841,34 @@ static class Map { CssKeywords.Baseline, FlexContentMode.Baseline }, { CssKeywords.Stretch, FlexContentMode.Stretch }, }; + + /// + /// Contains the string-BookmarkState mapping. + /// + public static readonly Dictionary BookmarkStates = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { CssKeywords.Open, BookmarkState.Open }, + { CssKeywords.Closed, BookmarkState.Closed }, + }; + + /// + /// Contains the string-FootnotePolicy mapping. + /// + public static readonly Dictionary FootnotePolicies = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { CssKeywords.Auto, FootnotePolicy.Auto }, + { CssKeywords.Block, FootnotePolicy.Block }, + { CssKeywords.Line, FootnotePolicy.Line }, + }; + + /// + /// Contains the string-FootnoteDisplay mapping. + /// + public static readonly Dictionary FootnoteDisplays = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { CssKeywords.Block, FootnoteDisplay.Block }, + { CssKeywords.Compact, FootnoteDisplay.Compact }, + { CssKeywords.Inline, FootnoteDisplay.Inline }, + }; } } diff --git a/src/AngleSharp.Css/Constants/PropertyNames.cs b/src/AngleSharp.Css/Constants/PropertyNames.cs index f887c2cd..5c2f412e 100644 --- a/src/AngleSharp.Css/Constants/PropertyNames.cs +++ b/src/AngleSharp.Css/Constants/PropertyNames.cs @@ -1306,5 +1306,40 @@ public static class PropertyNames /// The orientation declaration. /// public static readonly String Orientation = "orientation"; + + /// + /// The string-set declaration. + /// + public static readonly String StringSet = "string-set"; + + /// + /// The running declaration. + /// + public static readonly String Running = "running"; + + /// + /// The footnote-display declaration. + /// + public static readonly String FootnoteDisplay = "footnote-display"; + + /// + /// The footnote-policy declaration. + /// + public static readonly String FootnotePolicy = "footnote-policy"; + + /// + /// The bookmark-level declaration. + /// + public static readonly String BookmarkLevel = "bookmark-level"; + + /// + /// The bookmark-label declaration. + /// + public static readonly String BookmarkLabel = "bookmark-label"; + + /// + /// The bookmark-state declaration. + /// + public static readonly String BookmarkState = "bookmark-state"; } } diff --git a/src/AngleSharp.Css/CssConfigurationExtensions.cs b/src/AngleSharp.Css/CssConfigurationExtensions.cs index d0795928..f18f89d6 100644 --- a/src/AngleSharp.Css/CssConfigurationExtensions.cs +++ b/src/AngleSharp.Css/CssConfigurationExtensions.cs @@ -16,7 +16,7 @@ public static class CssConfigurationExtensions /// The configuration to extend. /// Optional options for the parser. /// The new instance with the service. - public static IConfiguration WithCss(this IConfiguration configuration, CssParserOptions options = default(CssParserOptions)) + public static IConfiguration WithCss(this IConfiguration configuration, CssParserOptions options = default) { configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); var service = new CssStylingService(); diff --git a/src/AngleSharp.Css/Declarations/BookmarkLabelDeclaration.cs b/src/AngleSharp.Css/Declarations/BookmarkLabelDeclaration.cs new file mode 100644 index 00000000..4e6e03fd --- /dev/null +++ b/src/AngleSharp.Css/Declarations/BookmarkLabelDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class BookmarkLabelDeclaration + { + public static String Name = PropertyNames.BookmarkLabel; + + public static IValueConverter Converter = Or(ContentListConverter, None); + + public static ICssValue InitialValue = InitialValues.BookmarkLabelDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/BookmarkLevelDeclaration.cs b/src/AngleSharp.Css/Declarations/BookmarkLevelDeclaration.cs new file mode 100644 index 00000000..a3852ccd --- /dev/null +++ b/src/AngleSharp.Css/Declarations/BookmarkLevelDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class BookmarkLevelDeclaration + { + public static String Name = PropertyNames.BookmarkLevel; + + public static IValueConverter Converter = PositiveIntegerConverter; + + public static ICssValue InitialValue = InitialValues.BookmarkLevelDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/BookmarkStateDeclaration.cs b/src/AngleSharp.Css/Declarations/BookmarkStateDeclaration.cs new file mode 100644 index 00000000..e0528a24 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/BookmarkStateDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class BookmarkStateDeclaration + { + public static String Name = PropertyNames.BookmarkState; + + public static IValueConverter Converter = BookmarkStateConverter; + + public static ICssValue InitialValue = InitialValues.BookmarkStateDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/BorderStyleDeclaration.cs b/src/AngleSharp.Css/Declarations/BorderStyleDeclaration.cs index a43a1619..32471f72 100644 --- a/src/AngleSharp.Css/Declarations/BorderStyleDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/BorderStyleDeclaration.cs @@ -21,8 +21,8 @@ static class BorderStyleDeclaration public static String[] Longhands = new[] { - PropertyNames.BorderRightStyle, PropertyNames.BorderTopStyle, + PropertyNames.BorderRightStyle, PropertyNames.BorderBottomStyle, PropertyNames.BorderLeftStyle, }; diff --git a/src/AngleSharp.Css/Declarations/ContentDeclaration.cs b/src/AngleSharp.Css/Declarations/ContentDeclaration.cs index 5e4b3e91..89255772 100644 --- a/src/AngleSharp.Css/Declarations/ContentDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/ContentDeclaration.cs @@ -26,7 +26,7 @@ sealed class ContentValueConverter : IValueConverter { CssKeywords.OpenQuote, new OpenQuoteContentMode() }, { CssKeywords.NoOpenQuote, new NoOpenQuoteContentMode() }, { CssKeywords.CloseQuote, new CloseQuoteContentMode() }, - { CssKeywords.NoCloseQuote, new NoCloseQuoteContentMode() } + { CssKeywords.NoCloseQuote, new NoCloseQuoteContentMode() }, }; public ICssValue Convert(StringSource source) @@ -78,7 +78,7 @@ public ICssValue Convert(StringSource source) if (a != null) { - ms.Add(new AttributeContentMode(a)); + ms.Add(new AttributeContentMode(a.Attribute)); source.SkipSpacesAndComments(); continue; } diff --git a/src/AngleSharp.Css/Declarations/FootnoteDisplayDeclaration.cs b/src/AngleSharp.Css/Declarations/FootnoteDisplayDeclaration.cs new file mode 100644 index 00000000..e0f27024 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/FootnoteDisplayDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class FootnoteDisplayDeclaration + { + public static String Name = PropertyNames.FootnoteDisplay; + + public static IValueConverter Converter = FootnoteDisplayConverter; + + public static ICssValue InitialValue = InitialValues.FootnoteDisplayDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/FootnotePolicyDeclaration.cs b/src/AngleSharp.Css/Declarations/FootnotePolicyDeclaration.cs new file mode 100644 index 00000000..e96e26dc --- /dev/null +++ b/src/AngleSharp.Css/Declarations/FootnotePolicyDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class FootnotePolicyDeclaration + { + public static String Name = PropertyNames.FootnotePolicy; + + public static IValueConverter Converter = FootnotePolicyConverter; + + public static ICssValue InitialValue = InitialValues.FootnotePolicyDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/MarginDeclaration.cs b/src/AngleSharp.Css/Declarations/MarginDeclaration.cs index 93ba9f93..d7062e04 100644 --- a/src/AngleSharp.Css/Declarations/MarginDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/MarginDeclaration.cs @@ -29,10 +29,7 @@ sealed class MarginAggregator : IValueAggregator, IValueConverter { private static readonly IValueConverter converter = AutoLengthOrPercentConverter.Periodic(); - public ICssValue Convert(StringSource source) - { - return converter.Convert(source); - } + public ICssValue Convert(StringSource source) => converter.Convert(source); public ICssValue Merge(ICssValue[] values) { diff --git a/src/AngleSharp.Css/Declarations/RunningDeclaration.cs b/src/AngleSharp.Css/Declarations/RunningDeclaration.cs new file mode 100644 index 00000000..e0f4efd9 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/RunningDeclaration.cs @@ -0,0 +1,17 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class RunningDeclaration + { + public static String Name = PropertyNames.Running; + + public static IValueConverter Converter = IdentifierConverter; + + public static ICssValue InitialValue = InitialValues.RunningDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/StringSetDeclaration.cs b/src/AngleSharp.Css/Declarations/StringSetDeclaration.cs new file mode 100644 index 00000000..00fbbdb2 --- /dev/null +++ b/src/AngleSharp.Css/Declarations/StringSetDeclaration.cs @@ -0,0 +1,20 @@ +namespace AngleSharp.Css.Declarations +{ + using AngleSharp.Css.Converters; + using AngleSharp.Css.Dom; + using System; + using static ValueConverters; + + static class StringSetDeclaration + { + public static String Name = PropertyNames.StringSet; + + public static IValueConverter Converter = Or( + WithOrder(IdentifierConverter, ContentListConverter).FromList(), + None); + + public static ICssValue InitialValue = InitialValues.StringSetDecl; + + public static PropertyFlags Flags = PropertyFlags.None; + } +} diff --git a/src/AngleSharp.Css/Declarations/TextDecorationDeclaration.cs b/src/AngleSharp.Css/Declarations/TextDecorationDeclaration.cs index 88f1c4ba..fe62075e 100644 --- a/src/AngleSharp.Css/Declarations/TextDecorationDeclaration.cs +++ b/src/AngleSharp.Css/Declarations/TextDecorationDeclaration.cs @@ -12,8 +12,8 @@ static class TextDecorationDeclaration public static IValueConverter Converter = AggregateTuple( WithAny( ColorConverter.Option(InitialValues.TextDecorationColorDecl), - TextDecorationStyleConverter.Option(InitialValues.TextDecorationLineDecl), - TextDecorationLinesConverter.Option(InitialValues.TextDecorationStyleDecl))); + TextDecorationStyleConverter.Option(InitialValues.TextDecorationStyleDecl), + TextDecorationLinesConverter.Option(InitialValues.TextDecorationLineDecl))); public static ICssValue InitialValue = null; @@ -22,8 +22,8 @@ static class TextDecorationDeclaration public static String[] Longhands = new[] { PropertyNames.TextDecorationColor, - PropertyNames.TextDecorationLine, PropertyNames.TextDecorationStyle, + PropertyNames.TextDecorationLine, }; } } diff --git a/src/AngleSharp.Css/Dom/BookmarkState.cs b/src/AngleSharp.Css/Dom/BookmarkState.cs new file mode 100644 index 00000000..1fe00b38 --- /dev/null +++ b/src/AngleSharp.Css/Dom/BookmarkState.cs @@ -0,0 +1,19 @@ +namespace AngleSharp.Css.Dom +{ + /// + /// The selected bookmark state. + /// + public enum BookmarkState + { + /// + /// The bookmarks of the nearest descendants of an element + /// with a bookmark-state of open will be displayed. + /// + Open, + /// + /// Any bookmarks of descendant elements are not initially + /// displayed. + /// + Closed, + } +} diff --git a/src/AngleSharp.Css/Dom/Floating.cs b/src/AngleSharp.Css/Dom/Floating.cs index 3c7de55c..2ed00f70 100644 --- a/src/AngleSharp.Css/Dom/Floating.cs +++ b/src/AngleSharp.Css/Dom/Floating.cs @@ -1,4 +1,4 @@ -namespace AngleSharp.Css.Dom +namespace AngleSharp.Css.Dom { /// /// All possible values for taking an element out of its normal flow. @@ -16,6 +16,10 @@ public enum Floating : byte /// /// Indicates that the element must float on the right side of its containing block. /// - Right + Right, + /// + /// Each footnote element is placed in the footnote area of the page. + /// + Footnote, } } diff --git a/src/AngleSharp.Css/Dom/FootnoteDisplay.cs b/src/AngleSharp.Css/Dom/FootnoteDisplay.cs new file mode 100644 index 00000000..640fbf54 --- /dev/null +++ b/src/AngleSharp.Css/Dom/FootnoteDisplay.cs @@ -0,0 +1,27 @@ +namespace AngleSharp.Css.Dom +{ + /// + /// Represents the selected footnote display mode. + /// + public enum FootnoteDisplay + { + /// + /// The footnote element is placed in the footnote + /// area as a block element. + /// + Block, + /// + /// The footnote element is placed in the footnote + /// area as an inline element. + /// + Inline, + /// + /// The user agent determines whether a given footnote + /// element is placed as a block element or an inline + /// element. If two or more footnotes could fit on the + /// same line in the footnote area, they should be + /// placed inline. + /// + Compact, + } +} diff --git a/src/AngleSharp.Css/Dom/FootnotePolicy.cs b/src/AngleSharp.Css/Dom/FootnotePolicy.cs new file mode 100644 index 00000000..ff98c11b --- /dev/null +++ b/src/AngleSharp.Css/Dom/FootnotePolicy.cs @@ -0,0 +1,32 @@ +namespace AngleSharp.Css.Dom +{ + /// + /// Represents the selected footnote policy. + /// + public enum FootnotePolicy + { + /// + /// The user agent chooses how to render footnotes, and may + /// place the footnote body on a later page than the footnote + /// reference. A footnote body must never be placed on a page + /// before the footnote reference. + /// + Auto, + /// + /// If a given footnote body cannot be placed on the current + /// page due to lack of space, the user agent introduces a + /// forced page break at the start of the line containing + /// the footnote reference, so that both the reference and + /// the footnote body fall on the next page. Note that the + /// user agent must honor widow and orphan settings when + /// doing this, and so may need to insert the page break + /// on an earlier line. + /// + Line, + /// + /// As with line, except a forced page break is introduced + /// before the paragraph that contains the footnote. + /// + Block, + } +} diff --git a/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs b/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs index 3a68f57a..c4707f64 100644 --- a/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs +++ b/src/AngleSharp.Css/Dom/Internal/PseudoElement.cs @@ -131,6 +131,8 @@ public String TextContent public IElement PreviousElementSibling => _host.PreviousElementSibling; + public NodeFlags Flags => _host.Flags; + #endregion #region Methods diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssImportRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssImportRule.cs index 1aec5a80..84674b88 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssImportRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssImportRule.cs @@ -1,6 +1,5 @@ -namespace AngleSharp.Css.Dom +namespace AngleSharp.Css.Dom { - using AngleSharp.Css.Extensions; using System; using System.IO; diff --git a/src/AngleSharp.Css/Dom/Internal/Rules/CssNamespaceRule.cs b/src/AngleSharp.Css/Dom/Internal/Rules/CssNamespaceRule.cs index b0d6db4b..1a9cbd43 100644 --- a/src/AngleSharp.Css/Dom/Internal/Rules/CssNamespaceRule.cs +++ b/src/AngleSharp.Css/Dom/Internal/Rules/CssNamespaceRule.cs @@ -1,6 +1,5 @@ -namespace AngleSharp.Css.Dom +namespace AngleSharp.Css.Dom { - using AngleSharp.Css.Extensions; using AngleSharp.Dom; using System; using System.IO; diff --git a/src/AngleSharp.Css/Dom/Internal/StyleCollection.cs b/src/AngleSharp.Css/Dom/Internal/StyleCollection.cs index 7aeea81d..cd58ccdb 100644 --- a/src/AngleSharp.Css/Dom/Internal/StyleCollection.cs +++ b/src/AngleSharp.Css/Dom/Internal/StyleCollection.cs @@ -1,5 +1,6 @@ namespace AngleSharp.Css.Dom { + using AngleSharp.Dom; using System.Collections; using System.Collections.Generic; @@ -39,7 +40,7 @@ public IEnumerator GetEnumerator() { if (!sheet.IsDisabled && sheet.Media.Validate(_device)) { - var rules = GetRules(sheet.Rules); + var rules = sheet.Rules.GetMatchingStyles(_device); foreach (var rule in rules) { @@ -53,45 +54,6 @@ public IEnumerator GetEnumerator() #region Helpers - private IEnumerable GetRules(ICssRuleList rules) - { - foreach (var rule in rules) - { - if (rule.Type == CssRuleType.Media) - { - var media = (ICssMediaRule)rule; - - if (media.IsValid(_device)) - { - var subrules = GetRules(media.Rules); - - foreach (var subrule in subrules) - { - yield return subrule; - } - } - } - else if (rule.Type == CssRuleType.Supports) - { - var support = (ICssSupportsRule)rule; - - if (support.IsValid(_device)) - { - var subrules = GetRules(support.Rules); - - foreach (var subrule in subrules) - { - yield return subrule; - } - } - } - else if (rule.Type == CssRuleType.Style) - { - yield return (ICssStyleRule)rule; - } - } - } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion diff --git a/src/AngleSharp.Css/Dom/StyleCollectionExtensions.cs b/src/AngleSharp.Css/Dom/StyleCollectionExtensions.cs index 3c5953c4..2f59380c 100644 --- a/src/AngleSharp.Css/Dom/StyleCollectionExtensions.cs +++ b/src/AngleSharp.Css/Dom/StyleCollectionExtensions.cs @@ -1,4 +1,4 @@ -namespace AngleSharp.Css.Extensions +namespace AngleSharp.Css { using AngleSharp.Css.Dom; using AngleSharp.Dom; @@ -72,8 +72,9 @@ public static ICssStyleDeclaration ComputeDeclarations(this StyleCollection rule /// /// The style rules to apply. /// The element to compute the cascade for. + /// The potential parent for the cascade. /// Returns the cascaded read-only style declaration. - public static ICssStyleDeclaration ComputeCascadedStyle(this StyleCollection styleCollection, IElement element) + public static ICssStyleDeclaration ComputeCascadedStyle(this StyleCollection styleCollection, IElement element, ICssStyleDeclaration parent = null) { var computedStyle = new CssStyleDeclaration(element.Owner?.Context); var rules = styleCollection.SortBySpecificity(element); @@ -89,6 +90,11 @@ public static ICssStyleDeclaration ComputeCascadedStyle(this StyleCollection sty computedStyle.SetDeclarations(element.GetStyle()); } + if (parent != null) + { + computedStyle.UpdateDeclarations(parent); + } + return computedStyle; } diff --git a/src/AngleSharp.Css/Dom/StyleDeclarationExtensions.cs b/src/AngleSharp.Css/Dom/StyleDeclarationExtensions.cs index d0826383..c058d7ba 100644 --- a/src/AngleSharp.Css/Dom/StyleDeclarationExtensions.cs +++ b/src/AngleSharp.Css/Dom/StyleDeclarationExtensions.cs @@ -3484,6 +3484,54 @@ public static String GetTextDecoration(this ICssStyleDeclaration style) => public static void SetTextDecoration(this ICssStyleDeclaration style, String value) => style.SetProperty(PropertyNames.TextDecoration, value); + /// + /// Gets a value that indicates the style of the text decoration. + /// + [DomName("textDecorationStyle")] + [DomAccessor(Accessors.Getter)] + public static String GetTextDecorationStyle(this ICssStyleDeclaration style) => + style.GetPropertyValue(PropertyNames.TextDecorationStyle); + + /// + /// Sets a value that indicates the style of the text decoration. + /// + [DomName("textDecorationStyle")] + [DomAccessor(Accessors.Setter)] + public static void SetTextDecorationStyle(this ICssStyleDeclaration style, String value) => + style.SetProperty(PropertyNames.TextDecorationStyle, value); + + /// + /// Gets a value that indicates the line of the text decoration. + /// + [DomName("textDecorationLine")] + [DomAccessor(Accessors.Getter)] + public static String GetTextDecorationLine(this ICssStyleDeclaration style) => + style.GetPropertyValue(PropertyNames.TextDecorationLine); + + /// + /// Sets a value that indicates the line of the text decoration. + /// + [DomName("textDecorationLine")] + [DomAccessor(Accessors.Setter)] + public static void SetTextDecorationLine(this ICssStyleDeclaration style, String value) => + style.SetProperty(PropertyNames.TextDecorationLine, value); + + /// + /// Gets a value that indicates the color of the text decoration. + /// + [DomName("textDecorationColor")] + [DomAccessor(Accessors.Getter)] + public static String GetTextDecorationColor(this ICssStyleDeclaration style) => + style.GetPropertyValue(PropertyNames.TextDecorationColor); + + /// + /// Sets a value that indicates the color of the text decoration. + /// + [DomName("textDecorationColor")] + [DomAccessor(Accessors.Setter)] + public static void SetTextDecorationColor(this ICssStyleDeclaration style, String value) => + style.SetProperty(PropertyNames.TextDecorationColor, value); + /// /// Gets the indentation of the first line of text in the /// object. diff --git a/src/AngleSharp.Css/Dom/StyleUtilityExtensions.cs b/src/AngleSharp.Css/Dom/StyleUtilityExtensions.cs index 645da0e2..8b983da0 100644 --- a/src/AngleSharp.Css/Dom/StyleUtilityExtensions.cs +++ b/src/AngleSharp.Css/Dom/StyleUtilityExtensions.cs @@ -1,7 +1,6 @@ namespace AngleSharp.Css.Dom { using AngleSharp.Attributes; - using AngleSharp.Css.Extensions; using AngleSharp.Dom; /// @@ -16,10 +15,8 @@ public static class StyleUtilityExtensions /// [DomName("cascadedStyle")] [DomAccessor(Accessors.Getter)] - public static ICssStyleDeclaration GetCascadedStyle(this IPseudoElement element) - { - return element.Owner.DefaultView.GetStyleCollection().ComputeCascadedStyle(element); - } + public static ICssStyleDeclaration GetCascadedStyle(this IPseudoElement element) => + element.Owner.DefaultView.GetStyleCollection().ComputeCascadedStyle(element); /// /// Gets a live CSS declaration block with only the default @@ -27,10 +24,8 @@ public static ICssStyleDeclaration GetCascadedStyle(this IPseudoElement element) /// [DomName("defaultStyle")] [DomAccessor(Accessors.Getter)] - public static ICssStyleDeclaration GetDefaultStyle(this IPseudoElement element) - { - return element.Owner.DefaultView.ComputeDefaultStyle(element); - } + public static ICssStyleDeclaration GetDefaultStyle(this IPseudoElement element) => + element.Owner.DefaultView.ComputeDefaultStyle(element); /// /// Gets a live CSS declaration block with properties @@ -38,9 +33,7 @@ public static ICssStyleDeclaration GetDefaultStyle(this IPseudoElement element) /// [DomName("rawComputedStyle")] [DomAccessor(Accessors.Getter)] - public static ICssStyleDeclaration GetRawComputedStyle(this IPseudoElement element) - { - return element.Owner.DefaultView.ComputeRawStyle(element); - } + public static ICssStyleDeclaration GetRawComputedStyle(this IPseudoElement element) => + element.Owner.DefaultView.ComputeRawStyle(element); } } diff --git a/src/AngleSharp.Css/Extensions/CssOmExtensions.cs b/src/AngleSharp.Css/Extensions/CssOmExtensions.cs index e8607017..e9fce611 100644 --- a/src/AngleSharp.Css/Extensions/CssOmExtensions.cs +++ b/src/AngleSharp.Css/Extensions/CssOmExtensions.cs @@ -64,5 +64,17 @@ public static ICssValue GetValueOf(this ICssStyleRule rule, String propertyName) var property = rule.Style.GetProperty(propertyName); return property?.RawValue; } + + /// + /// Computes the values with knowledge of the device. + /// + /// The base (raw) style. + /// The device to use for the calculation. + /// A new style declaration with the existing or computed values. + public static ICssStyleDeclaration Compute(this ICssStyleDeclaration style, IRenderDevice device) + { + //TODO + return style; + } } } diff --git a/src/AngleSharp.Css/Extensions/CssValueExtensions.cs b/src/AngleSharp.Css/Extensions/CssValueExtensions.cs index 4143c1eb..7f8eaa13 100644 --- a/src/AngleSharp.Css/Extensions/CssValueExtensions.cs +++ b/src/AngleSharp.Css/Extensions/CssValueExtensions.cs @@ -255,7 +255,7 @@ public static T AsEnum(this ICssValue value) return special.Value.AsEnum(); } - return default(T); + return default; } /// diff --git a/src/AngleSharp.Css/Extensions/ElementExtensions.cs b/src/AngleSharp.Css/Extensions/ElementExtensions.cs index 698e11a5..9eb91d6a 100644 --- a/src/AngleSharp.Css/Extensions/ElementExtensions.cs +++ b/src/AngleSharp.Css/Extensions/ElementExtensions.cs @@ -64,6 +64,7 @@ public static String GetInnerText(this IElement element) if (!hidden.Value) { + var offset = 0; var sb = StringBuilderPool.Obtain(); var requiredLineBreakCounts = new Dictionary(); InnerTextCollection(element, sb, requiredLineBreakCounts, element.ParentElement?.ComputeCurrentStyle()); @@ -71,7 +72,6 @@ public static String GetInnerText(this IElement element) // Remove any runs of consecutive required line break count items at the start or end of results. requiredLineBreakCounts.Remove(0); requiredLineBreakCounts.Remove(sb.Length); - var offset = 0; // SortedDictionary would be nicer foreach (var keyval in requiredLineBreakCounts.OrderBy(kv => kv.Key)) @@ -193,7 +193,10 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl if (node is IText textElement) { - ProcessText(textElement.Data, sb, parentStyle); + var lastLine = node.NextSibling is null || + String.IsNullOrEmpty(node.NextSibling.TextContent) || + node.NextSibling is IHtmlBreakRowElement; + ProcessText(textElement.Data, sb, parentStyle, lastLine); } else if (node is IHtmlBreakRowElement) { @@ -225,14 +228,14 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl } else if (node is IHtmlParagraphElement) { - requiredLineBreakCounts.TryGetValue(startIndex, out int startIndexCount); + requiredLineBreakCounts.TryGetValue(startIndex, out var startIndexCount); if (startIndexCount < 2) { requiredLineBreakCounts[startIndex] = 2; } - requiredLineBreakCounts.TryGetValue(sb.Length, out int endIndexCount); + requiredLineBreakCounts.TryGetValue(sb.Length, out var endIndexCount); if (endIndexCount < 2) { @@ -255,14 +258,14 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl if (isBlockLevel.Value) { - requiredLineBreakCounts.TryGetValue(startIndex, out int startIndexCount); + requiredLineBreakCounts.TryGetValue(startIndex, out var startIndexCount); if (startIndexCount < 1) { requiredLineBreakCounts[startIndex] = 1; } - requiredLineBreakCounts.TryGetValue(sb.Length, out int endIndexCount); + requiredLineBreakCounts.TryGetValue(sb.Length, out var endIndexCount); if (endIndexCount < 1) { @@ -387,7 +390,7 @@ private static Boolean IsBlockLevel(INode node) } } - private static void ProcessText(String text, StringBuilder sb, ICssStyleDeclaration style) + private static void ProcessText(String text, StringBuilder sb, ICssStyleDeclaration style, Boolean lastLine) { var startIndex = sb.Length; var whiteSpace = style?.GetWhiteSpace(); @@ -459,11 +462,13 @@ private static void ProcessText(String text, StringBuilder sb, ICssStyleDeclarat sb.Append(c); } - if (isWhiteSpace) // ended with whitespace + // ended with whitespace + if (isWhiteSpace && lastLine) { for (var offset = sb.Length - 1; offset >= startIndex; offset--) { var c = sb[offset]; + if (!Char.IsWhiteSpace(c) || c == Symbols.NoBreakSpace) { sb.Remove(offset + 1, sb.Length - 1 - offset); diff --git a/src/AngleSharp.Css/Extensions/StringExtensions.cs b/src/AngleSharp.Css/Extensions/StringExtensions.cs index 3dcf399e..b5f45fd5 100644 --- a/src/AngleSharp.Css/Extensions/StringExtensions.cs +++ b/src/AngleSharp.Css/Extensions/StringExtensions.cs @@ -52,7 +52,7 @@ public static String CssUnit(this String value, out Double result) } } - result = default(Double); + result = default; return null; } diff --git a/src/AngleSharp.Css/Extensions/StyleFormatterExtensions.cs b/src/AngleSharp.Css/Extensions/StyleFormatterExtensions.cs new file mode 100644 index 00000000..b5ea8e8b --- /dev/null +++ b/src/AngleSharp.Css/Extensions/StyleFormatterExtensions.cs @@ -0,0 +1,28 @@ +namespace AngleSharp.Css +{ + using System; + + /// + /// Extensions for IStyleFormattable instances. + /// + public static class StyleFormatterExtensions + { + /// + /// Returns a minified serialization of the node guided by the + /// MinifyStyleFormatter with the default options. + /// + /// The style node to format. + /// The source code snippet. + public static String Minify(this IStyleFormattable style) => + style.ToCss(new MinifyStyleFormatter()); + + /// + /// Returns a prettified serialization of the node guided by the + /// PrettyStyleFormatter with the default options. + /// + /// The style node to format. + /// The source code snippet. + public static String Prettify(this IStyleFormattable style) => + style.ToCss(new PrettyStyleFormatter()); + } +} diff --git a/src/AngleSharp.Css/Extensions/StyleSheetExtensions.cs b/src/AngleSharp.Css/Extensions/StyleSheetExtensions.cs index cf486916..8fdcca1f 100644 --- a/src/AngleSharp.Css/Extensions/StyleSheetExtensions.cs +++ b/src/AngleSharp.Css/Extensions/StyleSheetExtensions.cs @@ -1,5 +1,6 @@ namespace AngleSharp.Dom { + using AngleSharp.Css; using AngleSharp.Css.Dom; using System; using System.Collections.Generic; @@ -16,13 +17,58 @@ public static class StyleSheetExtensions /// The type of rules to get. /// The list of stylesheets to consider. /// The list of rules. - public static IEnumerable RulesOf(this IEnumerable sheets) + public static IEnumerable GetRules(this IEnumerable sheets) where TRule : ICssRule { sheets = sheets ?? throw new ArgumentNullException(nameof(sheets)); return sheets.Where(m => !m.IsDisabled).OfType().SelectMany(m => m.Rules).OfType(); } + /// + /// Gets the styles matching the given render device. + /// + /// The set of rules. + /// The render device. + /// The style rules. + public static IEnumerable GetMatchingStyles(this ICssRuleList rules, IRenderDevice device) + { + foreach (var rule in rules) + { + if (rule.Type == CssRuleType.Media) + { + var media = (ICssMediaRule)rule; + + if (media.IsValid(device)) + { + var subrules = media.Rules.GetMatchingStyles(device); + + foreach (var subrule in subrules) + { + yield return subrule; + } + } + } + else if (rule.Type == CssRuleType.Supports) + { + var support = (ICssSupportsRule)rule; + + if (support.IsValid(device)) + { + var subrules = support.Rules.GetMatchingStyles(device); + + foreach (var subrule in subrules) + { + yield return subrule; + } + } + } + else if (rule.Type == CssRuleType.Style) + { + yield return (ICssStyleRule)rule; + } + } + } + /// /// Gets all style rules that have the same selector text. /// @@ -33,7 +79,7 @@ public static IEnumerable StylesWith(this IEnumerable().Where(m => m.SelectorText == selectorText); + return sheets.GetRules().Where(m => m.SelectorText == selectorText); } /// diff --git a/src/AngleSharp.Css/Extensions/WindowExtensions.cs b/src/AngleSharp.Css/Extensions/WindowExtensions.cs index 79e75e1c..499eba65 100644 --- a/src/AngleSharp.Css/Extensions/WindowExtensions.cs +++ b/src/AngleSharp.Css/Extensions/WindowExtensions.cs @@ -1,8 +1,8 @@ namespace AngleSharp.Dom { using AngleSharp.Attributes; + using AngleSharp.Css; using AngleSharp.Css.Dom; - using AngleSharp.Css.Extensions; using System; /// diff --git a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs index 93580923..2aa16ed2 100644 --- a/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultDeclarationFactory.cs @@ -11,6 +11,55 @@ public class DefaultDeclarationFactory : IDeclarationFactory { private readonly Dictionary _declarations = new Dictionary(StringComparer.OrdinalIgnoreCase) { + { + BookmarkLabelDeclaration.Name, new DeclarationInfo( + name: BookmarkLabelDeclaration.Name, + converter: BookmarkLabelDeclaration.Converter, + initialValue: BookmarkLabelDeclaration.InitialValue, + flags: BookmarkLabelDeclaration.Flags) + }, + { + BookmarkLevelDeclaration.Name, new DeclarationInfo( + name: BookmarkLevelDeclaration.Name, + converter: BookmarkLevelDeclaration.Converter, + initialValue: BookmarkLevelDeclaration.InitialValue, + flags: BookmarkLevelDeclaration.Flags) + }, + { + BookmarkStateDeclaration.Name, new DeclarationInfo( + name: BookmarkStateDeclaration.Name, + converter: BookmarkStateDeclaration.Converter, + initialValue: BookmarkStateDeclaration.InitialValue, + flags: BookmarkStateDeclaration.Flags) + }, + { + FootnoteDisplayDeclaration.Name, new DeclarationInfo( + name: FootnoteDisplayDeclaration.Name, + converter: FootnoteDisplayDeclaration.Converter, + initialValue: FootnoteDisplayDeclaration.InitialValue, + flags: FootnoteDisplayDeclaration.Flags) + }, + { + FootnotePolicyDeclaration.Name, new DeclarationInfo( + name: FootnotePolicyDeclaration.Name, + converter: FootnotePolicyDeclaration.Converter, + initialValue: FootnotePolicyDeclaration.InitialValue, + flags: FootnotePolicyDeclaration.Flags) + }, + { + RunningDeclaration.Name, new DeclarationInfo( + name: RunningDeclaration.Name, + converter: RunningDeclaration.Converter, + initialValue: RunningDeclaration.InitialValue, + flags: RunningDeclaration.Flags) + }, + { + StringSetDeclaration.Name, new DeclarationInfo( + name: StringSetDeclaration.Name, + converter: StringSetDeclaration.Converter, + initialValue: StringSetDeclaration.InitialValue, + flags: StringSetDeclaration.Flags) + }, { CaptionSideDeclaration.Name, new DeclarationInfo( name: CaptionSideDeclaration.Name, diff --git a/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs b/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs index cc2ef389..95501300 100644 --- a/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultDocumentFunctionFactory.cs @@ -62,7 +62,7 @@ public Creator Unregister(String name) /// The default document function. protected virtual IDocumentFunction CreateDefault(String name, String url) { - return default(IDocumentFunction); + return default; } /// diff --git a/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs b/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs index 13e5cabb..187abdea 100644 --- a/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultFeatureValidatorFactory.cs @@ -96,7 +96,7 @@ public Creator Unregister(String name) /// The feature validator. protected virtual IFeatureValidator CreateDefault(String name) { - return default(IFeatureValidator); + return default; } /// diff --git a/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs b/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs index 7daa458b..b9e496e8 100644 --- a/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs +++ b/src/AngleSharp.Css/Factories/DefaultPseudoElementFactory.cs @@ -62,7 +62,7 @@ public Creator Unregister(String type) /// The default pseudo element instance. protected virtual IPseudoElement CreateDefault(IElement host, String type) { - return default(IPseudoElement); + return default; } /// diff --git a/src/AngleSharp.Css/MinifyStyleFormatter.cs b/src/AngleSharp.Css/MinifyStyleFormatter.cs new file mode 100644 index 00000000..3b6b3f2f --- /dev/null +++ b/src/AngleSharp.Css/MinifyStyleFormatter.cs @@ -0,0 +1,167 @@ +namespace AngleSharp.Css +{ + using AngleSharp.Css.Dom; + using AngleSharp.Text; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + + /// + /// Represents the an CSS3 markup formatter with minimal code. + /// + public class MinifyStyleFormatter : IStyleFormatter + { + #region Properties + + /// + /// Gets or sets if comments should be preserved. + /// + public Boolean ShouldKeepComments { get; set; } + + /// + /// Gets or sets if empty (zero-length) rules should be kept. + /// + public Boolean ShouldKeepEmptyRules { get; set; } + + #endregion + + #region Methods + + String IStyleFormatter.Sheet(IEnumerable rules) + { + if (ShouldKeepEmptyRules || IsNotEmpty(rules)) + { + var sb = StringBuilderPool.Obtain(); + + using (var writer = new StringWriter(sb)) + { + foreach (var rule in rules) + { + rule.ToCss(writer, this); + } + } + + return sb.ToPool(); + } + + return String.Empty; + } + + String IStyleFormatter.BlockRules(IEnumerable rules) + { + if (ShouldKeepEmptyRules || IsNotEmpty(rules)) + { + var sb = StringBuilderPool.Obtain().Append(Symbols.CurlyBracketOpen); + + using (var writer = new StringWriter(sb)) + { + foreach (var rule in rules) + { + rule.ToCss(writer, this); + } + } + + return sb.Append(Symbols.CurlyBracketClose).ToPool(); + } + + return String.Empty; + } + + String IStyleFormatter.Declaration(String name, String value, Boolean important) => + String.Concat(name, ":", String.Concat(value, important ? "!important" : String.Empty)); + + String IStyleFormatter.BlockDeclarations(IEnumerable declarations) + { + if (ShouldKeepEmptyRules || declarations.OfType().Any()) + { + var sb = StringBuilderPool.Obtain().Append(Symbols.CurlyBracketOpen); + + using (var writer = new StringWriter(sb)) + { + foreach (var declaration in declarations) + { + declaration.ToCss(writer, this); + writer.Write(Symbols.Semicolon); + } + + if (sb.Length > 1) + { + sb.Remove(sb.Length - 1, 1); + } + } + + return sb.Append(Symbols.CurlyBracketClose).ToPool(); + } + + return String.Empty; + } + + String IStyleFormatter.Rule(String name, String value) => + CssStyleFormatter.Instance.Rule(name, value); + + String IStyleFormatter.Rule(String name, String prelude, String rules) => + String.IsNullOrEmpty(rules) ? String.Empty : String.Concat(name, String.IsNullOrEmpty(prelude) ? String.Empty : " " + prelude, rules); + + String IStyleFormatter.Comment(String data) => + ShouldKeepComments ? CssStyleFormatter.Instance.Comment(data) : String.Empty; + + #endregion + + #region Helpers + + private static Boolean IsNotEmpty(IEnumerable rules) + { + foreach (var rule in rules.OfType()) + { + switch (rule.Type) + { + case CssRuleType.Document: + case CssRuleType.Supports: + case CssRuleType.Media: + if (IsNotEmpty(((ICssGroupingRule)rule).Rules)) + { + return true; + } + break; + case CssRuleType.Keyframes: + if (IsNotEmpty(((ICssKeyframesRule)rule).Rules)) + { + return true; + } + break; + case CssRuleType.FontFace: + if (((ICssFontFaceRule)rule).Style.Any()) + { + return true; + } + break; + case CssRuleType.Page: + if (((ICssPageRule)rule).Style.Any()) + { + return true; + } + break; + case CssRuleType.Style: + if (((ICssStyleRule)rule).Style.Any()) + { + return true; + } + break; + case CssRuleType.Keyframe: + if (((ICssKeyframeRule)rule).Style.Any()) + { + return true; + } + break; + default: + return true; + } + } + + return false; + } + + #endregion + } +} diff --git a/src/AngleSharp.Css/Parser/CssParser.cs b/src/AngleSharp.Css/Parser/CssParser.cs index 5b56e2e8..f95bede9 100644 --- a/src/AngleSharp.Css/Parser/CssParser.cs +++ b/src/AngleSharp.Css/Parser/CssParser.cs @@ -71,7 +71,7 @@ public CssParser() /// /// The options to use. public CssParser(CssParserOptions options) - : this(options, default(IBrowsingContext)) + : this(options, default) { } @@ -80,7 +80,7 @@ public CssParser(CssParserOptions options) /// /// The context to use. internal CssParser(IBrowsingContext context) - : this(default(CssParserOptions), context) + : this(default, context) { } @@ -282,7 +282,7 @@ private T Parse(String source, Func create) var token = tokenizer.Get(); var builder = new CssBuilder(_options, tokenizer, _context); var rule = create.Invoke(builder, token); - return tokenizer.Get().Type == CssTokenType.EndOfFile ? rule : default(T); + return tokenizer.Get().Type == CssTokenType.EndOfFile ? rule : default; } private T Parse(String source, Func> create) @@ -291,7 +291,7 @@ private T Parse(String source, Func> var token = tokenizer.Get(); var builder = new CssBuilder(_options, tokenizer, _context); var pair = create.Invoke(builder, token); - return pair.Item2.Type == CssTokenType.EndOfFile ? pair.Item1 : default(T); + return pair.Item2.Type == CssTokenType.EndOfFile ? pair.Item1 : default; } private CssTokenizer CreateTokenizer(String sourceCode) diff --git a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs index 339f0c9e..e788baf8 100644 --- a/src/AngleSharp.Css/Parser/Micro/ColorParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/ColorParser.cs @@ -45,9 +45,7 @@ static class ColorParser { if (source.Current == Symbols.RoundBracketOpen) { - var function = default(Func); - - if (ColorFunctions.TryGetValue(ident, out function)) + if (ColorFunctions.TryGetValue(ident, out var function)) { source.SkipCurrentAndSpaces(); return function.Invoke(source); @@ -68,7 +66,6 @@ static class ColorParser private static Color? Literal(StringSource source) { var current = source.Next(); - var result = default(Color); var buffer = StringBuilderPool.Obtain(); while (current.IsHex()) @@ -77,7 +74,7 @@ static class ColorParser current = source.Next(); } - if (Color.TryFromHex(buffer.ToPool(), out result)) + if (Color.TryFromHex(buffer.ToPool(), out var result)) { return result; } @@ -120,9 +117,9 @@ static class ColorParser { var h = ParseAngle(source); var c1 = source.SkipGetSkip(); - var s = ParsePercent(source); + var s = source.ParsePercent(); var c2 = source.SkipGetSkip(); - var l = ParsePercent(source); + var l = source.ParsePercent(); var c3 = source.SkipGetSkip(); if (h != null && s != null && l != null) @@ -172,9 +169,9 @@ static class ColorParser { var h = ParseAngle(source); var c1 = source.SkipGetSkip(); - var s = ParsePercent(source); + var s = source.ParsePercent(); var c2 = source.SkipGetSkip(); - var l = ParsePercent(source); + var l = source.ParsePercent(); var c3 = source.SkipGetSkip(); if (h != null && s != null && l != null) @@ -199,26 +196,13 @@ static class ColorParser return null; } - private static Double? ParsePercent(StringSource source) - { - var unit = source.ParseUnit(); - - if (unit != null && unit.Dimension == "%") - { - return Double.Parse(unit.Value, CultureInfo.InvariantCulture) * 0.01f; - } - - return null; - } - private static Byte? ParseRgbComponent(StringSource source) { var unit = source.ParseUnit(); - if (unit != null) + if (unit != null && + Int32.TryParse(unit.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) { - var value = Int32.Parse(unit.Value, CultureInfo.InvariantCulture); - if (unit.Dimension == "%") { return (Byte)(255f / 100f * value); @@ -236,17 +220,16 @@ static class ColorParser { var unit = source.ParseUnit(); - if (unit != null) + if (unit != null && + Double.TryParse(unit.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(unit.Value, CultureInfo.InvariantCulture); - if (unit.Dimension == "%") { - return value * 0.01f; + return value * 0.01; } else if (unit.Dimension == String.Empty) { - return Math.Max(Math.Min(value, 1f), 0f); + return Math.Max(Math.Min(value, 1.0), 0.0); } } @@ -257,13 +240,12 @@ static class ColorParser { var unit = source.ParseUnit(); - if (unit != null) + if (unit != null && + Double.TryParse(unit.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(unit.Value, CultureInfo.InvariantCulture); var dim = Angle.Unit.Deg; - if (unit.Dimension == String.Empty || - (dim = Angle.GetUnit(unit.Dimension)) != Angle.Unit.None) + if (unit.Dimension == String.Empty || (dim = Angle.GetUnit(unit.Dimension)) != Angle.Unit.None) { var angle = new Angle(value, dim); return angle.ToTurns(); @@ -273,12 +255,10 @@ static class ColorParser return null; } - private static Boolean Check(Char closingBracket, Char firstComma = Symbols.Comma, Char secondComma = Symbols.Comma, Char thirdComma = Symbols.Comma) - { - return closingBracket == Symbols.RoundBracketClose && - firstComma == Symbols.Comma && - secondComma == Symbols.Comma && - thirdComma == Symbols.Comma; - } + private static Boolean Check(Char closingBracket, Char firstComma = Symbols.Comma, Char secondComma = Symbols.Comma, Char thirdComma = Symbols.Comma) => + closingBracket == Symbols.RoundBracketClose && + firstComma == Symbols.Comma && + secondComma == Symbols.Comma && + thirdComma == Symbols.Comma; } } diff --git a/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs b/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs index 4b039eda..dc739028 100644 --- a/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/CssUriParser.cs @@ -207,7 +207,7 @@ private static CssUrlValue Bad(StringSource source, StringBuilder buffer) { ++round; } - else if (curly == Symbols.CurlyBracketOpen) + else if (current == Symbols.CurlyBracketOpen) { ++curly; } diff --git a/src/AngleSharp.Css/Parser/Micro/FunctionParser.cs b/src/AngleSharp.Css/Parser/Micro/FunctionParser.cs index 4c142973..a8649e82 100644 --- a/src/AngleSharp.Css/Parser/Micro/FunctionParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/FunctionParser.cs @@ -11,7 +11,7 @@ static class FunctionParser /// Represents an attribute retriever object. /// http://dev.w3.org/csswg/css-values/#funcdef-attr /// - public static String ParseAttr(this StringSource source) + public static CssAttrValue ParseAttr(this StringSource source) { if (source.IsFunction(FunctionNames.Attr)) { @@ -20,7 +20,7 @@ public static String ParseAttr(this StringSource source) if (content != null && f == Symbols.RoundBracketClose) { - return content; + return new CssAttrValue(content); } } @@ -97,6 +97,38 @@ public static CssVarValue ParseVar(this StringSource source) return null; } + public static CssContentValue ParseContent(this StringSource source) + { + if (source.IsFunction(FunctionNames.Content)) + { + var name = source.ParseCustomIdent(); + var f = source.SkipGetSkip(); + + if (name != null && f == Symbols.RoundBracketClose) + { + return new CssContentValue(name); + } + } + + return null; + } + + public static CssRunningValue ParseRunning(this StringSource source) + { + if (source.IsFunction(FunctionNames.Running)) + { + var name = source.ParseCustomIdent(); + var f = source.SkipGetSkip(); + + if (name != null && f == Symbols.RoundBracketClose) + { + return new CssRunningValue(name); + } + } + + return null; + } + /// /// Represents a counter object. /// http://www.w3.org/TR/CSS2/syndata.html#value-def-counter diff --git a/src/AngleSharp.Css/Parser/Micro/NumberParser.cs b/src/AngleSharp.Css/Parser/Micro/NumberParser.cs index 9e283a68..65a01adf 100644 --- a/src/AngleSharp.Css/Parser/Micro/NumberParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/NumberParser.cs @@ -10,10 +10,10 @@ static class NumberParser public static Double? ParseNumber(this StringSource source) { var unit = source.ParseUnit(); - var result = default(Double); - if (unit != null && unit.Dimension == String.Empty && - Double.TryParse(unit.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) + if (unit != null && + unit.Dimension == String.Empty && + Double.TryParse(unit.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -124,13 +124,12 @@ static class NumberParser public static Int32? ParseInteger(this StringSource source) { var unit = source.ParseUnit(); - var result = default(Int32); if (unit != null && unit.Dimension == String.Empty) { var value = unit.Value; - if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) + if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } diff --git a/src/AngleSharp.Css/Parser/Micro/PointParser.cs b/src/AngleSharp.Css/Parser/Micro/PointParser.cs index fe2ff8fe..d8a22d7c 100644 --- a/src/AngleSharp.Css/Parser/Micro/PointParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/PointParser.cs @@ -7,15 +7,11 @@ namespace AngleSharp.Css.Parser static class PointParser { - public static ICssValue ParsePointX(this StringSource source) - { - return source.ParsePointDir(IsHorizontal); - } + public static ICssValue ParsePointX(this StringSource source) => + source.ParsePointDir(IsHorizontal); - public static ICssValue ParsePointY(this StringSource source) - { - return source.ParsePointDir(IsVertical); - } + public static ICssValue ParsePointY(this StringSource source) => + source.ParsePointDir(IsVertical); private static ICssValue ParsePointDir(this StringSource source, Predicate checkKeyword) { @@ -167,15 +163,11 @@ public static CssBackgroundSizeValue ParseSize(this StringSource source) } } - private static Boolean IsHorizontal(String str) - { - return str.Isi(CssKeywords.Left) || str.Isi(CssKeywords.Right) || str.Isi(CssKeywords.Center); - } + private static Boolean IsHorizontal(String str) => + str.Isi(CssKeywords.Left) || str.Isi(CssKeywords.Right) || str.Isi(CssKeywords.Center); - private static Boolean IsVertical(String str) - { - return str.Isi(CssKeywords.Top) || str.Isi(CssKeywords.Bottom) || str.Isi(CssKeywords.Center); - } + private static Boolean IsVertical(String str) => + str.Isi(CssKeywords.Top) || str.Isi(CssKeywords.Bottom) || str.Isi(CssKeywords.Center); private static Length? KeywordToLength(String keyword) { diff --git a/src/AngleSharp.Css/Parser/Micro/UnitParser.cs b/src/AngleSharp.Css/Parser/Micro/UnitParser.cs index 5cc8f74f..7b707132 100644 --- a/src/AngleSharp.Css/Parser/Micro/UnitParser.cs +++ b/src/AngleSharp.Css/Parser/Micro/UnitParser.cs @@ -42,34 +42,28 @@ public static ICssValue ParseAutoLength(this StringSource source) return null; } - public static ICssValue ParseBorderWidth(this StringSource source) - { - return source.ParseLengthOrCalc() ?? - source.ParsePercentOrNumber() ?? - source.ParseAutoLength(); - } + public static ICssValue ParseBorderWidth(this StringSource source) => + source.ParseLengthOrCalc() ?? + source.ParsePercentOrNumber() ?? + source.ParseAutoLength(); - public static ICssValue ParseLineWidth(this StringSource source) - { - return source.ParseLengthOrCalc() ?? - source.ParseConstant(Map.BorderWidths); - } + public static ICssValue ParseLineWidth(this StringSource source) => + source.ParseLengthOrCalc() ?? + source.ParseConstant(Map.BorderWidths); - public static ICssValue ParseLineHeight(this StringSource source) - { - return source.ParseLengthOrCalc() ?? - source.ParsePercentOrNumber() ?? - source.ParseNormalLength(); - } + public static ICssValue ParseLineHeight(this StringSource source) => + source.ParseLengthOrCalc() ?? + source.ParsePercentOrNumber() ?? + source.ParseNormalLength(); public static Double? ParsePercent(this StringSource source) { var pos = source.Index; var test = source.ParseUnit(); - if (test != null && test.Dimension == "%") + if (test?.Dimension == "%" && + Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); return value * 0.01; } @@ -82,10 +76,8 @@ public static ICssValue ParseLineHeight(this StringSource source) var pos = source.Index; var test = source.ParseUnit(); - if (test != null) + if (test != null && Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); - if (test.Dimension == "%") { return new Length(value, Length.Unit.Percent); @@ -109,9 +101,9 @@ public static ICssValue ParseLineHeight(this StringSource source) { var unit = Angle.GetUnit(test.Dimension); - if (unit != Angle.Unit.None) + if (unit != Angle.Unit.None && + Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); return new Angle(value, unit); } @@ -121,10 +113,8 @@ public static ICssValue ParseLineHeight(this StringSource source) return null; } - public static ICssValue ParseAngleOrCalc(this StringSource source) - { - return source.ParseAngle().OrCalc(source); - } + public static ICssValue ParseAngleOrCalc(this StringSource source) => + source.ParseAngle().OrCalc(source); public static Frequency? ParseFrequency(this StringSource source) { @@ -135,9 +125,9 @@ public static ICssValue ParseAngleOrCalc(this StringSource source) { var unit = Frequency.GetUnit(test.Dimension); - if (unit != Frequency.Unit.None) + if (unit != Frequency.Unit.None && + Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); return new Frequency(value, unit); } @@ -147,11 +137,9 @@ public static ICssValue ParseAngleOrCalc(this StringSource source) return null; } - public static ICssValue ParseFontSize(this StringSource source) - { - return source.ParseDistanceOrCalc() ?? - source.ParseConstant(Map.FontSizes); - } + public static ICssValue ParseFontSize(this StringSource source) => + source.ParseDistanceOrCalc() ?? + source.ParseConstant(Map.FontSizes); public static ICssValue ParseTrackBreadth(this StringSource source, Boolean flexible = true) { @@ -167,9 +155,9 @@ public static ICssValue ParseTrackBreadth(this StringSource source, Boolean flex { var unit = Fraction.GetUnit(test.Dimension); - if (flexible && unit != Fraction.Unit.None) + if (flexible && unit != Fraction.Unit.None && + Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); return new Fraction(value, unit); } } @@ -212,10 +200,8 @@ public static ICssValue ParseTrackBreadth(this StringSource source, Boolean flex return length; } - public static ICssValue ParseDistanceOrCalc(this StringSource source) - { - return source.ParseDistance().OrCalc(source); - } + public static ICssValue ParseDistanceOrCalc(this StringSource source) => + source.ParseDistance().OrCalc(source); public static Length? ParseLength(this StringSource source) { @@ -232,10 +218,8 @@ public static ICssValue ParseDistanceOrCalc(this StringSource source) return length; } - public static ICssValue ParseLengthOrCalc(this StringSource source) - { - return source.ParseLength().OrCalc(source); - } + public static ICssValue ParseLengthOrCalc(this StringSource source) => + source.ParseLength().OrCalc(source); public static Resolution? ParseResolution(this StringSource source) { @@ -246,9 +230,9 @@ public static ICssValue ParseLengthOrCalc(this StringSource source) { var unit = Resolution.GetUnit(test.Dimension); - if (unit != Resolution.Unit.None) + if (unit != Resolution.Unit.None && + Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); return new Resolution(value, unit); } @@ -267,9 +251,9 @@ public static ICssValue ParseLengthOrCalc(this StringSource source) { var unit = Time.GetUnit(test.Dimension); - if (unit != Time.Unit.None) + if (unit != Time.Unit.None && + Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); return new Time(value, unit); } @@ -286,10 +270,10 @@ public static ICssValue ParseTimeOrCalc(this StringSource source) private static Length? GetLength(Unit test) { - if (test != null) + if (test != null && + Double.TryParse(test.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var value)) { var unit = Length.Unit.Px; - var value = Double.Parse(test.Value, CultureInfo.InvariantCulture); if ((test.Dimension == String.Empty && test.Value == "0") || (unit = Length.GetUnit(test.Dimension)) != Length.Unit.None) diff --git a/src/AngleSharp.Css/RenderTree/ElementRenderNode.cs b/src/AngleSharp.Css/RenderTree/ElementRenderNode.cs new file mode 100644 index 00000000..ad6363f3 --- /dev/null +++ b/src/AngleSharp.Css/RenderTree/ElementRenderNode.cs @@ -0,0 +1,22 @@ +namespace AngleSharp.Css.RenderTree +{ + using AngleSharp.Css.Dom; + using AngleSharp.Dom; + using System.Collections.Generic; + using System.Linq; + + class ElementRenderNode : IRenderNode + { + public INode Ref { get; set; } + + public IEnumerable Children { get; set; } = Enumerable.Empty(); + + public ICssStyleDeclaration SpecifiedStyle { get; set; } + + public ICssStyleDeclaration ComputedStyle { get; set; } + + public RenderValues UsedValue { get; set; } + + public RenderValues ActualValue { get; set; } + } +} diff --git a/src/AngleSharp.Css/RenderTree/IRenderNode.cs b/src/AngleSharp.Css/RenderTree/IRenderNode.cs new file mode 100644 index 00000000..a445c261 --- /dev/null +++ b/src/AngleSharp.Css/RenderTree/IRenderNode.cs @@ -0,0 +1,12 @@ +namespace AngleSharp.Css.RenderTree +{ + using AngleSharp.Dom; + using System.Collections.Generic; + + interface IRenderNode + { + INode Ref { get; } + + IEnumerable Children { get; } + } +} diff --git a/src/AngleSharp.Css/RenderTree/RenderTreeBuilder.cs b/src/AngleSharp.Css/RenderTree/RenderTreeBuilder.cs new file mode 100644 index 00000000..f8614a2c --- /dev/null +++ b/src/AngleSharp.Css/RenderTree/RenderTreeBuilder.cs @@ -0,0 +1,63 @@ +namespace AngleSharp.Css.RenderTree +{ + using AngleSharp.Css.Dom; + using AngleSharp.Dom; + using System.Collections.Generic; + using System.Linq; + + class RenderTreeBuilder + { + private readonly IWindow _window; + private readonly IEnumerable _defaultSheets; + private readonly IRenderDevice _device; + + public RenderTreeBuilder(IWindow window) + { + var ctx = window.Document.Context; + var defaultStyleSheetProvider = ctx.GetServices(); + _device = ctx.GetService(); + _defaultSheets = defaultStyleSheetProvider.Select(m => m.Default).Where(m => m != null); + _window = window; + } + + public IRenderNode RenderDocument() + { + var document = _window.Document; + var currentSheets = document.GetStyleSheets().OfType(); + var stylesheets = _defaultSheets.Concat(currentSheets); + var collection = new StyleCollection(stylesheets, _device); + return RenderElement(document.DocumentElement, collection); + } + + private ElementRenderNode RenderElement(IElement reference, StyleCollection collection, ICssStyleDeclaration parent = null) + { + var style = collection.ComputeCascadedStyle(reference, parent); + var children = new List(); + + foreach (var child in reference.ChildNodes) + { + if (child is IText text) + { + children.Add(RenderText(text, collection)); + } + else if (child is IElement element) + { + children.Add(RenderElement(element, collection, style)); + } + } + + return new ElementRenderNode + { + Ref = reference, + SpecifiedStyle = style, + ComputedStyle = style.Compute(_device), + Children = children, + }; + } + + private IRenderNode RenderText(IText text, StyleCollection collection) => new TextRenderNode + { + Ref = text, + }; + } +} diff --git a/src/AngleSharp.Css/RenderTree/RenderValues.cs b/src/AngleSharp.Css/RenderTree/RenderValues.cs new file mode 100644 index 00000000..3f8d3c95 --- /dev/null +++ b/src/AngleSharp.Css/RenderTree/RenderValues.cs @@ -0,0 +1,13 @@ +namespace AngleSharp.Css.RenderTree +{ + using AngleSharp.Css.Values; + + class RenderValues + { + public Color Color { get; set; } + + public Length Width { get; set; } + + public Length Height { get; set; } + } +} diff --git a/src/AngleSharp.Css/RenderTree/TextRenderNode.cs b/src/AngleSharp.Css/RenderTree/TextRenderNode.cs new file mode 100644 index 00000000..9dc7f36d --- /dev/null +++ b/src/AngleSharp.Css/RenderTree/TextRenderNode.cs @@ -0,0 +1,13 @@ +namespace AngleSharp.Css.RenderTree +{ + using AngleSharp.Dom; + using System.Collections.Generic; + using System.Linq; + + class TextRenderNode : IRenderNode + { + public INode Ref { get; set; } + + public IEnumerable Children => Enumerable.Empty(); + } +} diff --git a/src/AngleSharp.Css/ValueConverters.cs b/src/AngleSharp.Css/ValueConverters.cs index f21ca3e2..9a5adfd7 100644 --- a/src/AngleSharp.Css/ValueConverters.cs +++ b/src/AngleSharp.Css/ValueConverters.cs @@ -235,6 +235,26 @@ static class ValueConverters /// public static readonly IValueConverter ShapeConverter = FromParser(ShapeParser.ParseShape); + /// + /// Creates a converter for the content function. + /// + public static readonly IValueConverter ContentConverter = FromParser(FunctionParser.ParseContent); + + /// + /// Creates a converter for the attr function. + /// + public static readonly IValueConverter AttrConverter = FromParser(FunctionParser.ParseAttr); + + /// + /// Creates a converter for the counter(s) function. + /// + public static readonly IValueConverter CounterConverter = FromParser(FunctionParser.ParseCounter); + + /// + /// Creates a converter for the running function. + /// + public static readonly IValueConverter RunningConverter = FromParser(FunctionParser.ParseRunning); + #endregion #region Maps @@ -383,7 +403,7 @@ static class ValueConverters /// /// Represents a converter for the PositionMode enumeration. /// - public static readonly IValueConverter PositionModeConverter = Map.PositionModes.ToConverter(); + public static readonly IValueConverter PositionModeConverter = Or(Map.PositionModes.ToConverter(), RunningConverter); /// /// Represents a converter for the OverflowMode enumeration. @@ -520,6 +540,21 @@ static class ValueConverters /// public static readonly IValueConverter FlexWrapConverter = Map.FlexWrapModes.ToConverter(); + /// + /// Represents a converter for the BookmarkState enumeration. + /// + public static readonly IValueConverter BookmarkStateConverter = Map.BookmarkStates.ToConverter(); + + /// + /// Represents a converter for the FootnoteDisplay enumeration. + /// + public static readonly IValueConverter FootnoteDisplayConverter = Map.FootnoteDisplays.ToConverter(); + + /// + /// Represents a converter for the FootnotePolicy enumeration. + /// + public static readonly IValueConverter FootnotePolicyConverter = Map.FootnotePolicies.ToConverter(); + #endregion #region Specific @@ -716,6 +751,11 @@ static class ValueConverters /// public static readonly IValueConverter BackgroundRepeatsConverter = FromParser(CompoundParser.ParseBackgroundRepeat); + /// + /// Represents a converter for the content-list. + /// + public static readonly IValueConverter ContentListConverter = Or(StringConverter, CounterConverter, ContentConverter, AttrConverter).Many(); + #endregion #region Toggles @@ -862,6 +902,9 @@ public static IValueConverter WithBorderSide(ICssValue lineWidth, ICssValue line #region Helpers + private static IValueConverter FromParser(Func converter) + where T : struct, ICssValue => new StructValueConverter(converter); + private static IValueConverter FromParser(Func converter) where T : class, ICssValue => new ClassValueConverter(converter); diff --git a/src/AngleSharp.Css/Values/Functions/CssAttrValue.cs b/src/AngleSharp.Css/Values/Functions/CssAttrValue.cs new file mode 100644 index 00000000..9dc9cd67 --- /dev/null +++ b/src/AngleSharp.Css/Values/Functions/CssAttrValue.cs @@ -0,0 +1,55 @@ +namespace AngleSharp.Css.Values +{ + using AngleSharp.Css.Dom; + using AngleSharp.Text; + using System; + + /// + /// Represents a CSS attr function call. + /// + sealed class CssAttrValue : ICssFunctionValue + { + #region Fields + + private readonly String _attribute; + + #endregion + + #region ctor + + /// + /// Creates a new attr function call. + /// + /// The referenced attribute name. + public CssAttrValue(String attribute) + { + _attribute = attribute; + } + + #endregion + + #region Properties + + /// + /// Gets the used attribute. + /// + public String Attribute => _attribute; + + /// + /// Gets the name of the function. + /// + public String Name => FunctionNames.Attr; + + /// + /// Gets the arguments. + /// + public ICssValue[] Arguments => new ICssValue[] { new Identifier(_attribute) }; + + /// + /// Gets the CSS text representation. + /// + public String CssText => Name.CssFunction(_attribute); + + #endregion + } +} diff --git a/src/AngleSharp.Css/Values/Functions/CssContentValue.cs b/src/AngleSharp.Css/Values/Functions/CssContentValue.cs new file mode 100644 index 00000000..a90f869c --- /dev/null +++ b/src/AngleSharp.Css/Values/Functions/CssContentValue.cs @@ -0,0 +1,55 @@ +namespace AngleSharp.Css.Values +{ + using AngleSharp.Css.Dom; + using AngleSharp.Text; + using System; + + /// + /// Represents a CSS content function call. + /// + sealed class CssContentValue : ICssFunctionValue + { + #region Fields + + private readonly String _type; + + #endregion + + #region ctor + + /// + /// Creates a new content function call. + /// + /// The used dimension argument. + public CssContentValue(String type) + { + _type = type; + } + + #endregion + + #region Properties + + /// + /// Gets the used type. + /// + public String Type => _type; + + /// + /// Gets the name of the function. + /// + public String Name => FunctionNames.Content; + + /// + /// Gets the arguments. + /// + public ICssValue[] Arguments => new ICssValue[] { new Identifier(_type) }; + + /// + /// Gets the CSS text representation. + /// + public String CssText => Name.CssFunction(_type); + + #endregion + } +} diff --git a/src/AngleSharp.Css/Values/Functions/CssRunningValue.cs b/src/AngleSharp.Css/Values/Functions/CssRunningValue.cs new file mode 100644 index 00000000..674fb67d --- /dev/null +++ b/src/AngleSharp.Css/Values/Functions/CssRunningValue.cs @@ -0,0 +1,55 @@ +namespace AngleSharp.Css.Values +{ + using AngleSharp.Css.Dom; + using AngleSharp.Text; + using System; + + /// + /// Represents a CSS running function call. + /// + sealed class CssRunningValue : ICssFunctionValue + { + #region Fields + + private readonly String _ident; + + #endregion + + #region ctor + + /// + /// Creates a new running function call. + /// + /// The used identifier argument. + public CssRunningValue(String ident) + { + _ident = ident; + } + + #endregion + + #region Properties + + /// + /// Gets the used identifier. + /// + public String Identifier => _ident; + + /// + /// Gets the name of the function. + /// + public String Name => FunctionNames.Running; + + /// + /// Gets the arguments. + /// + public ICssValue[] Arguments => new ICssValue[] { new Identifier(_ident) }; + + /// + /// Gets the CSS text representation. + /// + public String CssText => Name.CssFunction(_ident); + + #endregion + } +} diff --git a/src/AngleSharp.Css/Values/Multiples/CssPeriodicValue.cs b/src/AngleSharp.Css/Values/Multiples/CssPeriodicValue.cs index 0d7b24f8..63c7a4ab 100644 --- a/src/AngleSharp.Css/Values/Multiples/CssPeriodicValue.cs +++ b/src/AngleSharp.Css/Values/Multiples/CssPeriodicValue.cs @@ -90,7 +90,7 @@ public String CssText } /// - public T Top => _values.Length > 0 ? _values[0] : default(T); + public T Top => _values.Length > 0 ? _values[0] : default; /// public T Right => _values.Length > 1 ? _values[1] : Top; diff --git a/src/AngleSharp.Css/Values/Multiples/CssRadiusValue.cs b/src/AngleSharp.Css/Values/Multiples/CssRadiusValue.cs index 09e43b78..b560f2c4 100644 --- a/src/AngleSharp.Css/Values/Multiples/CssRadiusValue.cs +++ b/src/AngleSharp.Css/Values/Multiples/CssRadiusValue.cs @@ -74,7 +74,7 @@ public String CssText } /// - public T Width => _values.Length > 0 ? _values[0] : default(T); + public T Width => _values.Length > 0 ? _values[0] : default; /// public T Height => _values.Length > 1 ? _values[1] : Width; diff --git a/src/AngleSharp.Css/Values/Primitives/Angle.cs b/src/AngleSharp.Css/Values/Primitives/Angle.cs index 5c249e00..ed55ff46 100644 --- a/src/AngleSharp.Css/Values/Primitives/Angle.cs +++ b/src/AngleSharp.Css/Values/Primitives/Angle.cs @@ -164,7 +164,7 @@ public static Boolean TryParse(String s, out Angle result) return true; } - result = default(Angle); + result = default; return false; } diff --git a/src/AngleSharp.Css/Values/Primitives/Fraction.cs b/src/AngleSharp.Css/Values/Primitives/Fraction.cs index 7ee67d30..eff5158d 100644 --- a/src/AngleSharp.Css/Values/Primitives/Fraction.cs +++ b/src/AngleSharp.Css/Values/Primitives/Fraction.cs @@ -85,7 +85,7 @@ public static Boolean TryParse(String s, out Fraction result) return true; } - result = default(Fraction); + result = default; return false; } diff --git a/src/AngleSharp.Css/Values/Primitives/Frequency.cs b/src/AngleSharp.Css/Values/Primitives/Frequency.cs index e37c4cb2..924657d5 100644 --- a/src/AngleSharp.Css/Values/Primitives/Frequency.cs +++ b/src/AngleSharp.Css/Values/Primitives/Frequency.cs @@ -127,7 +127,7 @@ public static Boolean TryParse(String s, out Frequency result) return true; } - result = default(Frequency); + result = default; return false; } diff --git a/src/AngleSharp.Css/Values/Primitives/Length.cs b/src/AngleSharp.Css/Values/Primitives/Length.cs index 87a647b8..2856b34d 100644 --- a/src/AngleSharp.Css/Values/Primitives/Length.cs +++ b/src/AngleSharp.Css/Values/Primitives/Length.cs @@ -236,7 +236,7 @@ public static Boolean TryParse(String s, out Length result) return true; } - result = default(Length); + result = default; return false; } diff --git a/src/AngleSharp.Css/Values/Primitives/Resolution.cs b/src/AngleSharp.Css/Values/Primitives/Resolution.cs index d8093e52..c0de9bd0 100644 --- a/src/AngleSharp.Css/Values/Primitives/Resolution.cs +++ b/src/AngleSharp.Css/Values/Primitives/Resolution.cs @@ -91,7 +91,7 @@ public static Boolean TryParse(String s, out Resolution result) return true; } - result = default(Resolution); + result = default; return false; } diff --git a/src/AngleSharp.Css/Values/Primitives/Time.cs b/src/AngleSharp.Css/Values/Primitives/Time.cs index 4f13c5ac..b56a46da 100644 --- a/src/AngleSharp.Css/Values/Primitives/Time.cs +++ b/src/AngleSharp.Css/Values/Primitives/Time.cs @@ -137,7 +137,7 @@ public static Boolean TryParse(String s, out Time result) return true; } - result = default(Time); + result = default; return false; } diff --git a/src/AngleSharp.Performance.Common/AngleSharp.Performance.Common.csproj b/src/AngleSharp.Performance.Common/AngleSharp.Performance.Common.csproj index a2556bdc..818b7046 100644 --- a/src/AngleSharp.Performance.Common/AngleSharp.Performance.Common.csproj +++ b/src/AngleSharp.Performance.Common/AngleSharp.Performance.Common.csproj @@ -1,6 +1,7 @@  netstandard2.0 + 7.1 diff --git a/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj b/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj index bacd4a0e..97417c08 100644 --- a/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj +++ b/src/AngleSharp.Performance.Css/AngleSharp.Performance.Css.csproj @@ -5,6 +5,7 @@ Exe false + 7.1 AnyCPU @@ -20,7 +21,7 @@ - + \ No newline at end of file diff --git a/src/AngleSharp.Performance.Utilities/AngleSharp.Performance.Utilities.csproj b/src/AngleSharp.Performance.Utilities/AngleSharp.Performance.Utilities.csproj index 61909064..e5086085 100644 --- a/src/AngleSharp.Performance.Utilities/AngleSharp.Performance.Utilities.csproj +++ b/src/AngleSharp.Performance.Utilities/AngleSharp.Performance.Utilities.csproj @@ -1,6 +1,7 @@  netstandard2.0 + 7.1 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e40eb8d9..dd2c65af 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,6 +2,6 @@ Extends the CSSOM from the core AngleSharp library. AngleSharp.Css - 0.12.1 + 0.13.0 \ No newline at end of file diff --git a/tools/anglesharp.cake b/tools/anglesharp.cake new file mode 100644 index 00000000..bb2e14a0 --- /dev/null +++ b/tools/anglesharp.cake @@ -0,0 +1,205 @@ +#addin "Cake.FileHelpers" +#addin "Octokit" +using Octokit; + +var configuration = Argument("configuration", "Release"); +var isRunningOnUnix = IsRunningOnUnix(); +var isRunningOnWindows = IsRunningOnWindows(); +var isRunningOnAppVeyor = AppVeyor.IsRunningOnAppVeyor; +var isPullRequest = AppVeyor.Environment.PullRequest.IsPullRequest; +var buildNumber = AppVeyor.Environment.Build.Number; +var releaseNotes = ParseReleaseNotes("./CHANGELOG.md"); +var version = releaseNotes.Version.ToString(); +var buildDir = Directory($"./src/{projectName}/bin") + Directory(configuration); +var buildResultDir = Directory("./bin") + Directory(version); +var nugetRoot = buildResultDir + Directory("nuget"); + +if (target == "PrePublish") +{ + version = $"{version}-alpha-{buildNumber}"; +} + +if (!isRunningOnWindows) +{ + frameworks.Remove("net46"); +} + +// Initialization +// ---------------------------------------- + +Setup(_ => +{ + Information($"Building version {version} of {projectName}."); + Information("For the publish target the following environment variables need to be set:"); + Information("- NUGET_API_KEY"); + Information("- GITHUB_API_TOKEN"); +}); + +// Tasks +// ---------------------------------------- + +Task("Clean") + .Does(() => + { + CleanDirectories(new DirectoryPath[] { buildDir, buildResultDir, nugetRoot }); + }); + +Task("Restore-Packages") + .IsDependentOn("Clean") + .Does(() => + { + NuGetRestore($"./src/{solutionName}.sln", new NuGetRestoreSettings + { + ToolPath = "tools/nuget.exe", + }); + }); + +Task("Build") + .IsDependentOn("Restore-Packages") + .Does(() => + { + ReplaceRegexInFiles("./src/Directory.Build.props", "(?<=)(.+?)(?=)", version); + DotNetCoreBuild($"./src/{solutionName}.sln", new DotNetCoreBuildSettings + { + Configuration = configuration, + }); + }); + +Task("Run-Unit-Tests") + .IsDependentOn("Build") + .Does(() => + { + var settings = new DotNetCoreTestSettings + { + Configuration = configuration, + }; + + if (isRunningOnAppVeyor) + { + settings.TestAdapterPath = Directory("."); + settings.Logger = "Appveyor"; + // TODO Finds a way to exclude tests not allowed to run on appveyor + // Not used in current code + //settings.Where = "cat != ExcludeFromAppVeyor"; + } + + DotNetCoreTest($"./src/{solutionName}.Tests/", settings); + }); + +Task("Copy-Files") + .IsDependentOn("Build") + .Does(() => + { + foreach (var item in frameworks) + { + var targetDir = nugetRoot + Directory("lib") + Directory(item.Key); + CreateDirectory(targetDir); + CopyFiles(new FilePath[] + { + buildDir + Directory(item.Value) + File($"{projectName}.dll"), + buildDir + Directory(item.Value) + File($"{projectName}.xml"), + }, targetDir); + } + + CopyFiles(new FilePath[] { $"src/{projectName}.nuspec" }, nugetRoot); + }); + +Task("Create-Package") + .IsDependentOn("Copy-Files") + .Does(() => + { + var nugetExe = GetFiles("./tools/**/nuget.exe").FirstOrDefault() + ?? (isRunningOnAppVeyor ? GetFiles("C:\\Tools\\NuGet3\\nuget.exe").FirstOrDefault() : null) + ?? throw new InvalidOperationException("Could not find nuget.exe."); + + var nuspec = nugetRoot + File($"{projectName}.nuspec"); + + NuGetPack(nuspec, new NuGetPackSettings + { + Version = version, + OutputDirectory = nugetRoot, + Symbols = false, + Properties = new Dictionary + { + { "Configuration", configuration }, + }, + }); + }); + +Task("Publish-Package") + .IsDependentOn("Create-Package") + .IsDependentOn("Run-Unit-Tests") + .Does(() => + { + var apiKey = EnvironmentVariable("NUGET_API_KEY"); + + if (String.IsNullOrEmpty(apiKey)) + { + throw new InvalidOperationException("Could not resolve the NuGet API key."); + } + + foreach (var nupkg in GetFiles(nugetRoot.Path.FullPath + "/*.nupkg")) + { + NuGetPush(nupkg, new NuGetPushSettings + { + Source = "https://nuget.org/api/v2/package", + ApiKey = apiKey, + }); + } + }); + +Task("Publish-Release") + .IsDependentOn("Publish-Package") + .IsDependentOn("Run-Unit-Tests") + .Does(() => + { + var githubToken = EnvironmentVariable("GITHUB_API_TOKEN"); + + if (String.IsNullOrEmpty(githubToken)) + { + throw new InvalidOperationException("Could not resolve GitHub token."); + } + + var github = new GitHubClient(new ProductHeaderValue("AngleSharpCakeBuild")) + { + Credentials = new Credentials(githubToken), + }; + + var newRelease = github.Repository.Release; + newRelease.Create("AngleSharp", projectName, new NewRelease("v" + version) + { + Name = version, + Body = String.Join(Environment.NewLine, releaseNotes.Notes), + Prerelease = false, + TargetCommitish = "master", + }).Wait(); + }); + +Task("Update-AppVeyor-Build-Number") + .WithCriteria(() => isRunningOnAppVeyor) + .Does(() => + { + var num = AppVeyor.Environment.Build.Number; + AppVeyor.UpdateBuildVersion($"{version}-{num}"); + }); + +// Targets +// ---------------------------------------- + +Task("Package") + .IsDependentOn("Run-Unit-Tests") + .IsDependentOn("Create-Package"); + +Task("Default") + .IsDependentOn("Package"); + +Task("Publish") + .IsDependentOn("Publish-Package") + .IsDependentOn("Publish-Release"); + +Task("PrePublish") + .IsDependentOn("Publish-Package"); + +Task("AppVeyor") + .IsDependentOn("Run-Unit-Tests") + .IsDependentOn("Update-AppVeyor-Build-Number");