├── .gitignore ├── LICENSE.txt ├── Less.Html.sln ├── Less.Html ├── CssInternal │ ├── Context.cs │ ├── PropertyReader.cs │ ├── ReaderBase.cs │ └── SelectorReader.cs ├── CssParser.cs ├── HtmlInternal │ ├── AttributeReader.cs │ ├── CloseScriptReader.cs │ ├── CloseStyleReader.cs │ ├── Context.cs │ ├── EndingReader.cs │ ├── ReaderBase.cs │ ├── TagMark.cs │ └── TagReader.cs ├── HtmlParser.cs ├── Less.Html.csproj ├── Less.Html.nuspec ├── Nodes │ ├── Attr.cs │ ├── Comment.cs │ ├── Document │ │ ├── Document.cs │ │ └── DocumentContent.cs │ ├── Element │ │ ├── Element.cs │ │ └── Indexes │ │ │ ├── IndexBase.cs │ │ │ ├── IndexOnClass.cs │ │ │ ├── IndexOnId.cs │ │ │ ├── IndexOnName.cs │ │ │ └── IndexOnTagName.cs │ ├── ElementCollection.cs │ ├── NamedNodeMap.cs │ ├── Node.cs │ ├── SelfCheckingException.cs │ └── Text.cs ├── Properties │ └── AssemblyInfo.cs ├── Query │ ├── ElementExtensions.cs │ ├── NodeExtensions.cs │ ├── Query.cs │ ├── QueryExtensions.cs │ ├── Selector.cs │ └── SelectorParam.cs ├── SelectorParser │ ├── AttrSelectorReader.cs │ ├── Context.cs │ ├── ElementFilter.cs │ ├── Filters │ │ ├── FilterByAll.cs │ │ ├── FilterByAttr.cs │ │ ├── FilterByClass.cs │ │ ├── FilterById.cs │ │ ├── FilterByOther.cs │ │ └── FilterByTagName.cs │ ├── ParamParser.cs │ ├── ReaderBase.cs │ ├── SelectorParamException.cs │ └── SelectorReader.cs ├── StyleParser.cs ├── Styles │ ├── Block.cs │ ├── BlockCollection.cs │ ├── Css.cs │ ├── CssInfo.cs │ ├── Properties │ │ ├── Background.cs │ │ ├── BackgroundImage.cs │ │ └── Src │ │ │ ├── Src.cs │ │ │ ├── SrcValue.cs │ │ │ └── SrcValueCollection.cs │ ├── Property.cs │ ├── PropertyCollection.cs │ ├── Style.cs │ └── StyleCollection.cs └── packages.config ├── README.md ├── Test ├── App.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Test.csproj ├── Test1.cs ├── Test2.cs ├── Test3.cs ├── Test4.cs ├── Test5.cs ├── Test6.cs ├── packages.config ├── testCss │ ├── app.cb1b40f2.css │ ├── bibaoke.com.css │ └── haidilao.com.css └── testHtml │ ├── 265.com.html │ ├── auto.qq.com.html │ ├── avon.co.za.error.html │ ├── avon.co.za.html │ ├── js.html │ ├── kayak.com.html │ ├── killedbypolice.net.html │ ├── lrcmyanmar.org.html │ ├── tsite.jp.html │ ├── wsbuluo.com.html │ ├── www.darc.de.html │ └── youpinai.com.html └── Test4 ├── App.config ├── Form1.Designer.cs ├── Form1.cs ├── Form1.resx ├── Program.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── Test4.csproj └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2017] [Shao Zhuang] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Less.Html.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Less.Html", "Less.Html\Less.Html.csproj", "{5749EA1F-89F8-4029-96BE-457170DE431A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{1C0BBB42-D921-4AC3-8A05-754E9553280D}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test4", "Test4\Test4.csproj", "{8A44E7D7-9117-4284-BFFA-C2BFE3BA4769}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {5749EA1F-89F8-4029-96BE-457170DE431A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {5749EA1F-89F8-4029-96BE-457170DE431A}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {5749EA1F-89F8-4029-96BE-457170DE431A}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {5749EA1F-89F8-4029-96BE-457170DE431A}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {1C0BBB42-D921-4AC3-8A05-754E9553280D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {1C0BBB42-D921-4AC3-8A05-754E9553280D}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {1C0BBB42-D921-4AC3-8A05-754E9553280D}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {1C0BBB42-D921-4AC3-8A05-754E9553280D}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {8A44E7D7-9117-4284-BFFA-C2BFE3BA4769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {8A44E7D7-9117-4284-BFFA-C2BFE3BA4769}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {8A44E7D7-9117-4284-BFFA-C2BFE3BA4769}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {8A44E7D7-9117-4284-BFFA-C2BFE3BA4769}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /Less.Html/CssInternal/Context.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | namespace Less.Html.CssInternal 4 | { 5 | internal class Context 6 | { 7 | internal Css Css 8 | { 9 | get; 10 | private set; 11 | } 12 | 13 | internal int Position 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | internal Style CurrentStyle 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | internal Block CurrentBlock 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | internal Context(string content) 32 | { 33 | this.Css = new Css(content); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Less.Html/CssInternal/PropertyReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Less.Html.CssInternal 7 | { 8 | internal class PropertyReader : ReaderBase 9 | { 10 | private static Regex Pattern 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | private static Regex NameValuePattern 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | static PropertyReader() 23 | { 24 | PropertyReader.Pattern = @" 25 | (\s*(?/\*.*?\*/))| 26 | (\s*(?.*?)\s*(?;|}))".ToRegex( 27 | RegexOptions.IgnorePatternWhitespace | 28 | RegexOptions.Singleline | 29 | RegexOptions.Compiled | 30 | RegexOptions.ExplicitCapture); 31 | 32 | PropertyReader.NameValuePattern = @"(?[\w-]+)\s*:\s*(?.+)\s*".ToRegex( 33 | RegexOptions.Singleline | 34 | RegexOptions.Compiled | 35 | RegexOptions.ExplicitCapture); 36 | } 37 | 38 | internal override ReaderBase Read() 39 | { 40 | Match match = PropertyReader.Pattern.Match(this.Content, this.Position); 41 | 42 | if (match.Success) 43 | { 44 | this.Ascend(match); 45 | 46 | Group comment = match.Groups["comment"]; 47 | 48 | if (comment.Success) 49 | { 50 | return this.Pass(); 51 | } 52 | else 53 | { 54 | Group property = match.Groups["property"]; 55 | Group ending = match.Groups["ending"]; 56 | 57 | Match matchNameValue = PropertyReader.NameValuePattern.Match(property.Value); 58 | 59 | if (matchNameValue.Success) 60 | { 61 | Group name = matchNameValue.Groups["name"]; 62 | Group value = matchNameValue.Groups["value"]; 63 | 64 | int valueBegin = property.Index + value.Index; 65 | 66 | Property p = new Property(this.Css, 67 | property.Index, 68 | property.Index + name.Length - 1, 69 | valueBegin, 70 | valueBegin + value.Length - 1, 71 | property.Index + property.Length - 1); 72 | 73 | this.CurrentStyle.Add(p); 74 | } 75 | 76 | if (ending.Value == ";") 77 | { 78 | return this.Pass(); 79 | } 80 | else 81 | { 82 | this.CurrentStyle.End = ending.Index; 83 | 84 | if (this.CurrentBlock.IsNull()) 85 | { 86 | this.Styles.Add(this.CurrentStyle); 87 | } 88 | else 89 | { 90 | this.CurrentBlock.Styles.Add(this.CurrentStyle); 91 | } 92 | 93 | return this.Pass(); 94 | } 95 | } 96 | } 97 | else 98 | { 99 | return null; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Less.Html/CssInternal/ReaderBase.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Less.Html.CssInternal 6 | { 7 | internal abstract class ReaderBase 8 | { 9 | internal Context Context 10 | { 11 | get; 12 | set; 13 | } 14 | 15 | internal Css Css 16 | { 17 | get 18 | { 19 | return this.Context.Css; 20 | } 21 | } 22 | 23 | internal string Content 24 | { 25 | get 26 | { 27 | return this.Css.Content; 28 | } 29 | } 30 | 31 | internal int Position 32 | { 33 | get 34 | { 35 | return this.Context.Position; 36 | } 37 | set 38 | { 39 | this.Context.Position = value; 40 | } 41 | } 42 | 43 | internal Style CurrentStyle 44 | { 45 | get 46 | { 47 | return this.Context.CurrentStyle; 48 | } 49 | set 50 | { 51 | this.Context.CurrentStyle = value; 52 | } 53 | } 54 | 55 | internal Block CurrentBlock 56 | { 57 | get 58 | { 59 | return this.Context.CurrentBlock; 60 | } 61 | set 62 | { 63 | this.Context.CurrentBlock = value; 64 | } 65 | } 66 | 67 | internal StyleCollection Styles 68 | { 69 | get 70 | { 71 | return this.Css.Styles; 72 | } 73 | } 74 | 75 | internal BlockCollection Blocks 76 | { 77 | get 78 | { 79 | return this.Css.Blocks; 80 | } 81 | } 82 | 83 | internal abstract ReaderBase Read(); 84 | 85 | protected T Pass() where T : ReaderBase, new() 86 | { 87 | T t = new T(); 88 | 89 | t.Context = this.Context; 90 | 91 | return t; 92 | } 93 | 94 | protected void Ascend(Match match) 95 | { 96 | this.Position = match.Index + match.Length; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Less.Html/CssInternal/SelectorReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | using Less.Text; 5 | 6 | namespace Less.Html.CssInternal 7 | { 8 | internal class SelectorReader : ReaderBase 9 | { 10 | private static Regex Pattern 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | private static Regex CloseBracePattern 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | static SelectorReader() 23 | { 24 | SelectorReader.Pattern = @" 25 | (\s*(?}))| 26 | (\s*/\*(?.*?)\*/)| 27 | (\s*(?@.*?)\s*{)| 28 | (\s*(?.*?)\s*{)".ToRegex( 29 | RegexOptions.IgnorePatternWhitespace | 30 | RegexOptions.Singleline | 31 | RegexOptions.Compiled | 32 | RegexOptions.ExplicitCapture); 33 | 34 | SelectorReader.CloseBracePattern = "}".ToRegex( 35 | RegexOptions.Singleline | 36 | RegexOptions.Compiled); 37 | } 38 | 39 | internal override ReaderBase Read() 40 | { 41 | Match match = SelectorReader.Pattern.Match(this.Content, this.Position); 42 | 43 | if (match.Success) 44 | { 45 | this.Ascend(match); 46 | 47 | Group close = match.Groups["close"]; 48 | 49 | if (close.Success) 50 | { 51 | if (this.CurrentBlock.IsNotNull()) 52 | { 53 | this.CurrentBlock.End = close.Index; 54 | 55 | this.Blocks.Add(this.CurrentBlock); 56 | 57 | this.CurrentBlock = null; 58 | } 59 | 60 | return this.Pass(); 61 | } 62 | else 63 | { 64 | Group comment = match.Groups["comment"]; 65 | 66 | if (comment.Success) 67 | { 68 | return this.Pass(); 69 | } 70 | else 71 | { 72 | Group at = match.Groups["at"]; 73 | 74 | if (at.Success) 75 | { 76 | if (at.Value.IsNotEmpty()) 77 | { 78 | string prefix = at.Value.Split('-')[0]; 79 | 80 | int endIndex = at.Index + at.Length - 1; 81 | 82 | if (prefix.CompareIgnoreCase("media")) 83 | { 84 | this.CurrentBlock = new Block(this.Css, at.Index, endIndex); 85 | 86 | return this.Pass(); 87 | } 88 | else 89 | { 90 | this.CurrentStyle = new Style(this.Css, at.Index, endIndex); 91 | 92 | return this.Pass(); 93 | } 94 | } 95 | else 96 | { 97 | return this.Ignore(); 98 | } 99 | } 100 | else 101 | { 102 | Group selector = match.Groups["selector"]; 103 | 104 | if (selector.Value.IsNotEmpty()) 105 | { 106 | this.CurrentStyle = new Style(this.Css, selector.Index, selector.Index + selector.Length - 1); 107 | 108 | return this.Pass(); 109 | } 110 | else 111 | { 112 | return this.Ignore(); 113 | } 114 | } 115 | } 116 | } 117 | } 118 | else 119 | { 120 | return null; 121 | } 122 | } 123 | 124 | private ReaderBase Ignore() 125 | { 126 | Match matchCloseBrace = SelectorReader.CloseBracePattern.Match(this.Content, this.Position); 127 | 128 | if (matchCloseBrace.Success) 129 | { 130 | this.Ascend(matchCloseBrace); 131 | 132 | return this.Pass(); 133 | } 134 | else 135 | { 136 | return null; 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Less.Html/CssParser.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Html.CssInternal; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// css 解析器 9 | /// 10 | public static class CssParser 11 | { 12 | /// 13 | /// 解析 14 | /// 15 | /// 16 | /// 17 | public static Css Parse(string content) 18 | { 19 | ReaderBase reader = new SelectorReader(); 20 | 21 | Context context = new Context(content); 22 | 23 | reader.Context = context; 24 | 25 | while (true) 26 | { 27 | reader = reader.Read(); 28 | 29 | if (reader.IsNull()) 30 | { 31 | break; 32 | } 33 | } 34 | 35 | return context.Css; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Less.Html/HtmlInternal/CloseScriptReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | using Less.Text; 5 | 6 | namespace Less.Html.HtmlInternal 7 | { 8 | /// 9 | /// script 闭标签阅读器 10 | /// 11 | internal class CloseScriptReader : ReaderBase 12 | { 13 | /// 14 | /// 正则表达式 15 | /// 16 | private static Regex Pattern 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | /// 23 | /// 初始化 24 | /// 25 | static CloseScriptReader() 26 | { 27 | CloseScriptReader.Pattern = @" 28 | script)((?\s)|(?/>)|(?>)) 29 | ".ToRegex( 30 | RegexOptions.IgnorePatternWhitespace | 31 | RegexOptions.IgnoreCase | 32 | RegexOptions.Compiled | 33 | RegexOptions.ExplicitCapture); 34 | } 35 | 36 | /// 37 | /// 阅读 38 | /// 39 | /// 40 | internal override ReaderBase Read() 41 | { 42 | //匹配 script 闭标签 43 | Match match = CloseScriptReader.Pattern.Match(this.Content, this.Position); 44 | 45 | //如果匹配成功 46 | if (match.Success) 47 | { 48 | //提升阅读位置 49 | this.Ascend(match); 50 | 51 | //加入文本节点 52 | this.AddText(match); 53 | 54 | //捕获的空白结束 55 | Group space = match.Groups["space"]; 56 | 57 | //如果标签未结束 58 | if (space.Success) 59 | { 60 | //读取属性 61 | return this.Pass().Set("script", match.Index); 62 | } 63 | //如果标签已结束 64 | else 65 | { 66 | //关闭标签 67 | return this.CloseTag("script", match.Index - 1); 68 | } 69 | } 70 | else 71 | { 72 | return this.Pass(); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Less.Html/HtmlInternal/CloseStyleReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | using Less.Text; 5 | 6 | namespace Less.Html.HtmlInternal 7 | { 8 | /// 9 | /// style 闭标签阅读器 10 | /// 11 | internal class CloseStyleReader : ReaderBase 12 | { 13 | /// 14 | /// 正则表达式 15 | /// 16 | private static Regex Pattern 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | /// 23 | /// 初始化 24 | /// 25 | static CloseStyleReader() 26 | { 27 | CloseStyleReader.Pattern = @" 28 | style)((?\s)|(?/>)|(?>)) 29 | ".ToRegex( 30 | RegexOptions.IgnorePatternWhitespace | 31 | RegexOptions.IgnoreCase | 32 | RegexOptions.Compiled | 33 | RegexOptions.ExplicitCapture); 34 | } 35 | 36 | /// 37 | /// 阅读 38 | /// 39 | /// 40 | internal override ReaderBase Read() 41 | { 42 | Match match = CloseStyleReader.Pattern.Match(this.Content, this.Position); 43 | 44 | //如果匹配成功 45 | if (match.Success) 46 | { 47 | //提升阅读位置 48 | this.Ascend(match); 49 | 50 | //加入文本节点 51 | this.AddText(match); 52 | 53 | //捕获的空白结束 54 | Group space = match.Groups["space"]; 55 | 56 | //如果标签未结束 57 | if (space.Success) 58 | { 59 | //读取属性 60 | return this.Pass().Set("style", match.Index); 61 | } 62 | //如果标签已结束 63 | else 64 | { 65 | //关闭标签 66 | return this.CloseTag("style", match.Index - 1); 67 | } 68 | } 69 | else 70 | { 71 | return this.Pass(); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Less.Html/HtmlInternal/Context.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Less.Html.HtmlInternal 7 | { 8 | /// 9 | /// Html 阅读器上下文 10 | /// 11 | internal class Context 12 | { 13 | /// 14 | /// 当前正在读取的节点 15 | /// 16 | internal Node CurrentNode 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | /// 23 | /// 整个 html 文档元素 24 | /// 25 | internal Document Document 26 | { 27 | get; 28 | private set; 29 | } 30 | 31 | /// 32 | /// 标签标记栈 只包括开标签 33 | /// 34 | internal Stack MarkStack 35 | { 36 | get; 37 | private set; 38 | } 39 | 40 | /// 41 | /// 上一个标签标记 包括开标签和闭标签 42 | /// 43 | internal TagMark Previous 44 | { 45 | get; 46 | set; 47 | } 48 | 49 | /// 50 | /// 当前正在读取的位置 51 | /// 52 | internal int Position 53 | { 54 | get; 55 | set; 56 | } 57 | 58 | /// 59 | /// 创建实例 60 | /// 61 | /// 62 | /// 63 | internal Context(string content, Func parse) 64 | { 65 | this.MarkStack = new Stack(); 66 | 67 | this.Document = new Document(content, parse); 68 | 69 | this.Previous = new TagMark(this.Document.nodeName, 0); 70 | 71 | this.CurrentNode = this.Document; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Less.Html/HtmlInternal/EndingReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Collection; 4 | 5 | namespace Less.Html.HtmlInternal 6 | { 7 | /// 8 | /// 文档结束阅读器 9 | /// 10 | internal class EndingReader : ReaderBase 11 | { 12 | /// 13 | /// 阅读 14 | /// 15 | /// 16 | internal override ReaderBase Read() 17 | { 18 | //处理没有关闭的标签 19 | //当前要处理的标签 20 | Node node = this.CurrentNode; 21 | 22 | //根据标记栈确定没有关闭标签的个数 23 | this.MarkStack.Count.Each(() => 24 | { 25 | //节点一定是元素 26 | Element element = (Element)node; 27 | 28 | //设置元素结束位置 29 | if (element.hasChildNodes()) 30 | { 31 | element.End = element.firstChild.Begin - 1; 32 | } 33 | else 34 | { 35 | element.End = this.Position - 1; 36 | } 37 | 38 | element.InnerEnd = element.End; 39 | 40 | //设置当前处理的标签为上一级节点 41 | node = node.parentNode; 42 | }); 43 | 44 | //文档结束索引 45 | int end = this.Content.Length - 1; 46 | 47 | //截取文档最后的文本 48 | if (end >= this.Previous.Position) 49 | { 50 | if (this.Document.all.Count > 0) 51 | { 52 | Element last = this.Document.all[this.Document.all.Count - 1]; 53 | 54 | if (last.End == 0) 55 | { 56 | this.CurrentNode.DiscardNode(last); 57 | 58 | return null; 59 | } 60 | } 61 | 62 | node.appendChild(new Text(this.Previous.Position, end)); 63 | } 64 | 65 | return null; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Less.Html/HtmlInternal/ReaderBase.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | using System.Text.RegularExpressions; 5 | using System.Linq; 6 | using Less.Text; 7 | using Less.Collection; 8 | 9 | namespace Less.Html.HtmlInternal 10 | { 11 | /// 12 | /// 阅读器 抽象类 13 | /// 14 | internal abstract class ReaderBase 15 | { 16 | /// 17 | /// 当前正在阅读的节点 18 | /// 19 | internal Node CurrentNode 20 | { 21 | get 22 | { 23 | return this.Context.CurrentNode; 24 | } 25 | set 26 | { 27 | this.Context.CurrentNode = value; 28 | } 29 | } 30 | 31 | /// 32 | /// 标签标记栈 只包括开标签 33 | /// 34 | internal Stack MarkStack 35 | { 36 | get 37 | { 38 | return this.Context.MarkStack; 39 | } 40 | } 41 | 42 | /// 43 | /// 上一个标签标记 包括开标签和闭标签 44 | /// 45 | internal TagMark Previous 46 | { 47 | get 48 | { 49 | return this.Context.Previous; 50 | } 51 | set 52 | { 53 | this.Context.Previous = value; 54 | } 55 | } 56 | 57 | /// 58 | /// 当前正在阅读的位置 59 | /// 60 | internal int Position 61 | { 62 | get 63 | { 64 | return this.Context.Position; 65 | } 66 | private set 67 | { 68 | this.Context.Position = value; 69 | } 70 | } 71 | 72 | /// 73 | /// 文档 74 | /// 75 | internal Document Document 76 | { 77 | get { return this.Context.Document; } 78 | } 79 | 80 | /// 81 | /// 文档内容 82 | /// 83 | internal string Content 84 | { 85 | get 86 | { 87 | return this.Document.Content; 88 | } 89 | } 90 | 91 | /// 92 | /// 阅读器上下文 93 | /// 94 | internal Context Context 95 | { 96 | get; 97 | set; 98 | } 99 | 100 | /// 101 | /// 执行阅读 102 | /// 103 | /// 104 | internal abstract ReaderBase Read(); 105 | 106 | /// 107 | /// 当前阅读器任务完成 把工作交给下一个阅读器 108 | /// 109 | /// 110 | /// 111 | protected T Pass() where T : ReaderBase, new() 112 | { 113 | T t = new T(); 114 | 115 | t.Context = this.Context; 116 | 117 | return t; 118 | } 119 | 120 | /// 121 | /// 提升阅读位置 122 | /// 123 | /// 124 | protected void Ascend(Match match) 125 | { 126 | //设置阅读位置 127 | this.Position = match.Index + match.Length; 128 | } 129 | 130 | /// 131 | /// 把开标签加入标记栈 132 | /// 133 | /// 134 | /// 135 | protected void Push(string name, int position) 136 | { 137 | this.MarkStack.Push(new TagMark(name, position)); 138 | } 139 | 140 | /// 141 | /// 加入文本节点 142 | /// 143 | /// 现匹配的标签 144 | protected void AddText(Match tag) 145 | { 146 | int end = tag.Index - 1; 147 | 148 | if (end >= this.Previous.Position) 149 | { 150 | this.CurrentNode.appendChild(new Text(this.Previous.Position, end)); 151 | } 152 | } 153 | 154 | protected void OpenTag(Element element) 155 | { 156 | //设置当前节点 157 | this.CurrentNode = element; 158 | 159 | //加入标记栈 160 | this.Push(element.Name, this.Position); 161 | } 162 | 163 | protected ReaderBase EndTag(string name) 164 | { 165 | //设置上一个标签 166 | this.Previous = new TagMark(name, this.Position); 167 | 168 | //script 标签的 innerHTML 都作为纯文本处理 169 | if (name.CompareIgnoreCase("script")) 170 | { 171 | return this.Pass(); 172 | } 173 | 174 | //style 标签的 innerHTML 都作为纯文本处理 175 | if (name.CompareIgnoreCase("style")) 176 | { 177 | return this.Pass(); 178 | } 179 | 180 | //读取下一个标签 181 | return this.Pass(); 182 | } 183 | 184 | protected TagReader CloseTag(string name, int innerEnd) 185 | { 186 | return this.CloseTag(name, innerEnd, this.Position - 1); 187 | } 188 | 189 | protected TagReader CloseTag(string name, int innerEnd, int end) 190 | { 191 | //设置上一个标签 192 | this.Previous = new TagMark(name, this.Position); 193 | 194 | //标记栈中有对应的开标签 195 | //完成此双标签的读取 196 | if (this.MarkStackExists(name)) 197 | { 198 | //从栈顶部开始找对应的开标签标记 199 | while (true) 200 | { 201 | Element element = (Element)this.CurrentNode; 202 | 203 | //设置元素结束位置 204 | element.End = end; 205 | 206 | element.InnerEnd = innerEnd; 207 | 208 | //设置当前节点为上一级节点 209 | this.CurrentNode = this.CurrentNode.parentNode; 210 | 211 | //从栈顶部取出开标签 没有闭标签的双标签会被自动结束 212 | //取出对应的开标签才跳出 213 | if (this.MarkStack.Pop().Name.CompareIgnoreCase(name)) 214 | { 215 | break; 216 | } 217 | } 218 | } 219 | 220 | //读取下一个标签 221 | return this.Pass(); 222 | } 223 | 224 | protected bool MarkStackLast(string name) 225 | { 226 | if (this.MarkStack.Count > 0) 227 | { 228 | TagMark mark = this.MarkStack.Peek(); 229 | 230 | if (mark.Name.CompareIgnoreCase(name)) 231 | { 232 | return true; 233 | } 234 | } 235 | 236 | return false; 237 | } 238 | 239 | protected bool MarkStackExists(string name) 240 | { 241 | bool exists = false; 242 | 243 | this.MarkStack.ToArray().EachDesc(item => 244 | { 245 | if (item.Name.CompareIgnoreCase(name)) 246 | { 247 | exists = true; 248 | 249 | return false; 250 | } 251 | else 252 | { 253 | return true; 254 | } 255 | }); 256 | 257 | return exists; 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /Less.Html/HtmlInternal/TagMark.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | namespace Less.Html.HtmlInternal 4 | { 5 | /// 6 | /// 标签标记 7 | /// 8 | internal class TagMark 9 | { 10 | /// 11 | /// 标签名 12 | /// 13 | internal string Name 14 | { 15 | get; 16 | private set; 17 | } 18 | 19 | /// 20 | /// 位置 标签结束时 解析器扫描到的位置 等于元素的 End + 1 21 | /// 22 | internal int Position 23 | { 24 | get; 25 | private set; 26 | } 27 | 28 | /// 29 | /// 创建实例 30 | /// 31 | /// 32 | /// 33 | internal TagMark(string name, int position) 34 | { 35 | this.Name = name; 36 | this.Position = position; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Less.Html/HtmlInternal/TagReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | using Less.Text; 5 | 6 | namespace Less.Html.HtmlInternal 7 | { 8 | /// 9 | /// 标签阅读器 10 | /// 11 | internal class TagReader : ReaderBase 12 | { 13 | /// 14 | /// 正则表达式 15 | /// 16 | private static Regex Pattern 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | /// 23 | /// 初始化 24 | /// 25 | static TagReader() 26 | { 27 | TagReader.Pattern = @" 28 | (?)| 29 | <(?[!a-zA-Z].*?)((?\s)|(?/>)|(?>))| 30 | [a-zA-Z].*?)((?\s)|(?/>)|(?>)) 31 | ".ToRegex( 32 | RegexOptions.IgnorePatternWhitespace | 33 | RegexOptions.Singleline | 34 | RegexOptions.Compiled | 35 | RegexOptions.ExplicitCapture); 36 | } 37 | 38 | /// 39 | /// 阅读 40 | /// 41 | /// 42 | internal override ReaderBase Read() 43 | { 44 | //匹配标签 或注释 45 | Match match = TagReader.Pattern.Match(this.Content, this.Position); 46 | 47 | //如果匹配成功 48 | if (match.Success) 49 | { 50 | //提升阅读位置 51 | this.Ascend(match); 52 | 53 | //捕获的注释 54 | Group comment = match.Groups["comment"]; 55 | 56 | //如果是注释 57 | if (comment.Success) 58 | { 59 | //加入文本节点 60 | this.AddText(match); 61 | 62 | //加入注释节点 63 | this.CurrentNode.appendChild(new Comment(match.Index, this.Position - 1)); 64 | 65 | //结束标签 66 | return this.EndTag(Comment.NodeName); 67 | } 68 | 69 | //捕获的开标签 70 | Group open = match.Groups["open"]; 71 | 72 | //如果是开标签 73 | if (open.Success) 74 | { 75 | //捕获的空白结束 76 | Group space = match.Groups["space"]; 77 | //捕获的开双标签结束 78 | Group xdouble = match.Groups["double"]; 79 | 80 | //加入文本节点 81 | this.AddText(match); 82 | 83 | //创建元素实例 84 | Element element = this.Document.createElement(open.Value); 85 | 86 | //设置元素起始位置 87 | element.Begin = match.Index; 88 | 89 | //如果标签未结束 90 | if (space.Success) 91 | { 92 | //对表格型元素的处理 93 | if (element.IsTable) 94 | { 95 | if (this.MarkStackExists(element.Name)) 96 | { 97 | int end = this.Previous.Position - 1; 98 | 99 | this.CloseTag(element.Name, end, end); 100 | } 101 | } 102 | 103 | //对枚举型元素的处理 104 | if (element.IsEnum) 105 | { 106 | if (this.MarkStackLast(element.Name)) 107 | { 108 | int end = this.Previous.Position - 1; 109 | 110 | this.CloseTag(element.Name, end, end); 111 | } 112 | } 113 | 114 | //加入节点 115 | this.CurrentNode.appendChild(element); 116 | 117 | //读取属性 118 | return this.Pass().Set(element); 119 | } 120 | //如果标签已结束 121 | else 122 | { 123 | TagReader reader = null; 124 | 125 | //对表格型元素的处理 126 | if (element.IsTable) 127 | { 128 | if (this.MarkStackExists(element.Name)) 129 | { 130 | int end = this.Previous.Position - 1; 131 | 132 | reader = this.CloseTag(element.Name, end, end); 133 | } 134 | } 135 | 136 | //对枚举型元素的处理 137 | if (element.IsEnum) 138 | { 139 | if (this.MarkStackLast(element.Name)) 140 | { 141 | int end = this.Previous.Position - 1; 142 | 143 | reader = this.CloseTag(element.Name, end, end); 144 | } 145 | } 146 | 147 | //加入节点 148 | this.CurrentNode.appendChild(element); 149 | 150 | element.InnerBegin = this.Position; 151 | 152 | //如果是双标签 153 | if (xdouble.Success) 154 | { 155 | //单标签元素 156 | if (element.IsSingle) 157 | { 158 | element.InnerEnd = this.Position - 1; 159 | 160 | element.End = element.InnerEnd; 161 | } 162 | //不是单标签元素 163 | else 164 | { 165 | //开标签 166 | this.OpenTag(element); 167 | } 168 | } 169 | //如果是单标签 170 | else 171 | { 172 | element.InnerEnd = this.Position - 1; 173 | 174 | element.End = element.InnerEnd; 175 | } 176 | 177 | if (reader.IsNull()) 178 | { 179 | //结束标签 180 | return this.EndTag(element.Name); 181 | } 182 | else 183 | { 184 | return reader; 185 | } 186 | } 187 | } 188 | //如果是闭标签 189 | else 190 | { 191 | //捕获的闭标签名 192 | string name = match.Groups["close"].Value; 193 | 194 | //捕获的空白结束 195 | Group closeSpace = match.Groups["space"]; 196 | 197 | //加入文本节点 198 | this.AddText(match); 199 | 200 | //如果闭标签未结束 201 | if (closeSpace.Success) 202 | { 203 | //读取属性 204 | return this.Pass().Set(name, match.Index); 205 | } 206 | //如果闭标签已结束 207 | else 208 | { 209 | //关闭标签 210 | return this.CloseTag(name, match.Index - 1); 211 | } 212 | } 213 | } 214 | else 215 | { 216 | return this.Pass(); 217 | } 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /Less.Html/HtmlParser.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using Less.Html.HtmlInternal; 6 | 7 | namespace Less.Html 8 | { 9 | /// 10 | /// html 解析器 11 | /// 12 | public static class HtmlParser 13 | { 14 | private static Dictionary Cache 15 | { 16 | get; 17 | set; 18 | } 19 | 20 | static HtmlParser() 21 | { 22 | HtmlParser.Cache = new Dictionary(); 23 | } 24 | 25 | /// 26 | /// 解析 html 27 | /// 返回选择器 28 | /// 29 | /// 要解析的 html 30 | /// 是否使用缓存 31 | /// jQuery 风格的 css 选择器 32 | public static Qfun Query(string content, bool cache) 33 | { 34 | Document document = HtmlParser.Parse(content, cache); 35 | 36 | return Selector.Bind(document); 37 | } 38 | 39 | /// 40 | /// 解析 html 41 | /// 返回选择器 42 | /// 43 | /// 要解析的 html 44 | /// jQuery 风格的 css 选择器 45 | public static Qfun Query(string content) 46 | { 47 | Document document = HtmlParser.Parse(content); 48 | 49 | return Selector.Bind(document); 50 | } 51 | 52 | /// 53 | /// 解析 html 54 | /// 返回文档 55 | /// 56 | /// 要解析的 html 57 | /// 是否使用缓存 58 | /// DOM 文档 59 | /// content 不能为 null 60 | public static Document Parse(string content, bool cache) 61 | { 62 | if (cache) 63 | { 64 | Document document; 65 | 66 | if (HtmlParser.Cache.TryGetValue(content, out document)) 67 | { 68 | Document clone = (Document)document.cloneNode(true); 69 | 70 | return clone; 71 | } 72 | else 73 | { 74 | document = HtmlParser.Parse(content); 75 | 76 | Document clone = (Document)document.cloneNode(true); 77 | 78 | try 79 | { 80 | HtmlParser.Cache.Add(content, clone); 81 | } 82 | catch (ArgumentException) 83 | { 84 | // 85 | } 86 | 87 | return document; 88 | } 89 | } 90 | else 91 | { 92 | return HtmlParser.Parse(content); 93 | } 94 | } 95 | 96 | /// 97 | /// 解析 html 98 | /// 返回文档 99 | /// 100 | /// 要解析的 html 101 | /// DOM 文档 102 | public static Document Parse(string content) 103 | { 104 | //创建标签阅读器 105 | ReaderBase reader = new TagReader(); 106 | 107 | //阅读器上下文 108 | Context context = new Context(content, c => HtmlParser.Parse(c)); 109 | 110 | //设置上下文 111 | reader.Context = context; 112 | 113 | //读取所有内容 114 | while (true) 115 | { 116 | //执行读取 返回下一个阅读器 117 | reader = reader.Read(); 118 | 119 | //不返回阅读器 读取完毕 跳出 120 | if (reader.IsNull()) 121 | { 122 | break; 123 | } 124 | } 125 | 126 | //返回文档元素 127 | return context.Document; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Less.Html/Less.Html.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5749EA1F-89F8-4029-96BE-457170DE431A} 8 | Library 9 | Properties 10 | Less.Html 11 | Less.Html 12 | v3.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | bin\Debug\Less.Html.XML 25 | true 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | bin\Release\Less.Html.XML 35 | 36 | 37 | 38 | ..\packages\Less.Common.1.0.94.1\lib\net35\Less.Common.dll 39 | 40 | 41 | ..\packages\Less.Log.1.0.1.1\lib\net35\Less.Log.dll 42 | 43 | 44 | ..\packages\Newtonsoft.Json.4.0.8\lib\net35\Newtonsoft.Json.dll 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | Designer 117 | 118 | 119 | 120 | 121 | 122 | 129 | -------------------------------------------------------------------------------- /Less.Html/Less.Html.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Shao Zhuang 8 | 9 | https://github.com/bibaoke/Less.Html/blob/master/LICENSE.txt 10 | http://bibaoke.com/Less.Html 11 | 12 | false 13 | $description$ 14 | 改进编辑文档的性能 15 | Copyright 2017 Shao Zhuang 16 | html-parser jQuery-style css-selector css-parser 17 | 18 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Comment.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 注释 9 | /// 10 | public class Comment : Node 11 | { 12 | /// 13 | /// 节点名称 14 | /// 15 | internal static string NodeName 16 | { 17 | get { return "#comment"; } 18 | } 19 | 20 | /// 21 | /// 节点名称 22 | /// 23 | public override string nodeName 24 | { 25 | get 26 | { 27 | return Comment.NodeName.ToUpper(); 28 | } 29 | } 30 | 31 | /// 32 | /// 节点值 33 | /// 34 | public override string nodeValue 35 | { 36 | get 37 | { 38 | int length = this.End - this.Begin + 1; 39 | 40 | return this.ownerDocument.Content.SubstringUnsafe(this.Begin, length); 41 | } 42 | } 43 | 44 | internal Comment(int begin, int end) : base(begin, end) 45 | { 46 | // 47 | } 48 | 49 | /// 50 | /// 克隆节点 51 | /// 52 | /// 53 | /// 54 | public override Node cloneNode(bool deep) 55 | { 56 | return this.ownerDocument.Parse(this.Content).firstChild; 57 | } 58 | 59 | internal override Node Clone(Node parent) 60 | { 61 | Comment clone = new Comment(this.Begin, this.End); 62 | 63 | clone.parentNode = parent; 64 | 65 | clone.ChildIndex = this.ChildIndex; 66 | 67 | parent.ChildNodeList.Add(clone); 68 | 69 | clone.Index = this.Index; 70 | 71 | parent.ownerDocument.AllNodes.Add(clone); 72 | 73 | clone.ownerDocument = parent.ownerDocument; 74 | 75 | return clone; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Document/Document.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Linq; 4 | using System; 5 | using System.Collections.Generic; 6 | using Less.Text; 7 | using Less.Collection; 8 | 9 | namespace Less.Html 10 | { 11 | /// 12 | /// 文档 13 | /// 14 | public class Document : Node 15 | { 16 | internal override int Begin 17 | { 18 | get 19 | { 20 | return 0; 21 | } 22 | set 23 | { 24 | //不能设置文档的索引 25 | } 26 | } 27 | 28 | internal override int End 29 | { 30 | get 31 | { 32 | return this.Content.Length - 1; 33 | } 34 | set 35 | { 36 | //不能设置文档的索引 37 | } 38 | } 39 | 40 | /// 41 | /// 文档内容 42 | /// 43 | internal new DocumentContent Content 44 | { 45 | get; 46 | set; 47 | } 48 | 49 | /// 50 | /// html 解析委托 51 | /// 52 | internal Func Parse 53 | { 54 | get; 55 | private set; 56 | } 57 | 58 | internal List AllNodes 59 | { 60 | get; 61 | private set; 62 | } 63 | 64 | /// 65 | /// 节点名称 66 | /// 67 | public override string nodeName 68 | { 69 | get 70 | { 71 | return "#DOCUMENT"; 72 | } 73 | } 74 | 75 | /// 76 | /// 返回对文档中所有 area 和 a 元素 77 | /// 78 | public Element[] links 79 | { 80 | get 81 | { 82 | Element[] elements = this.getElementsByTagName("a").ExtArray(this.getElementsByTagName("area")); 83 | 84 | return elements.Where(i => i.attributes["href"].IsNotNull()).ToArray(); 85 | } 86 | } 87 | 88 | /// 89 | /// 所有元素 90 | /// 91 | public ElementCollection all 92 | { 93 | get; 94 | private set; 95 | } 96 | 97 | private Document(string content, Func parse, ElementCollection all) 98 | { 99 | this.AllNodes = new List(); 100 | 101 | this.Index = 0; 102 | 103 | this.AllNodes.Add(this); 104 | 105 | this.all = all; 106 | 107 | //设置文档内容 108 | this.Content = content; 109 | 110 | //文档的文档元素是本身 111 | this.ownerDocument = this; 112 | 113 | this.Parse = parse; 114 | } 115 | 116 | internal Document(string content, Func parse) : this(content, parse, new ElementCollection()) 117 | { 118 | // 119 | } 120 | 121 | /// 122 | /// 输出字符串 123 | /// 124 | /// 125 | public override string ToString() 126 | { 127 | return this.Content; 128 | } 129 | 130 | /// 131 | /// 克隆节点 132 | /// 133 | /// 134 | /// 135 | public override Node cloneNode(bool deep) 136 | { 137 | if (deep) 138 | { 139 | return this.Clone(null); 140 | } 141 | else 142 | { 143 | return new Document("", this.Parse); 144 | } 145 | } 146 | 147 | /// 148 | /// 返回对拥有指定 id 的第一个元素 149 | /// 150 | /// 151 | /// 152 | public Element getElementById(string id) 153 | { 154 | return this.all.GetElementById(id); 155 | } 156 | 157 | /// 158 | /// 返回带有指定名称的元素 159 | /// 160 | /// 161 | /// 162 | public Element[] getElementsByName(string name) 163 | { 164 | return this.all.GetElementsByName(name); 165 | } 166 | 167 | /// 168 | /// 返回带有指定标签名的元素 169 | /// 170 | /// 171 | /// 172 | public Element[] getElementsByTagName(string tagName) 173 | { 174 | return this.all.GetElementsByTagName(tagName); 175 | } 176 | 177 | /// 178 | /// 创建元素 179 | /// 180 | /// 181 | /// 182 | public Element createElement(string name) 183 | { 184 | return new Element(name); 185 | } 186 | 187 | internal override Node Clone(Node parent) 188 | { 189 | Document clone = new Document(this.Content, this.Parse, this.all.Clone()); 190 | 191 | clone.AllNodesCount = this.AllNodesCount; 192 | 193 | clone.AllNodes.Capacity = this.AllNodes.Capacity; 194 | 195 | clone.ChildNodeList.Capacity = this.ChildNodeList.Capacity; 196 | 197 | foreach (Node i in this.ChildNodeList) 198 | { 199 | Node child = i.Clone(clone); 200 | } 201 | 202 | return clone; 203 | } 204 | 205 | /// 206 | /// 添加子节点时执行 207 | /// 208 | /// 209 | protected override void OnAppendChild(Node node) 210 | { 211 | //把元素添加到文档的 all 集合 212 | if (node is Element) 213 | { 214 | Element element = (Element)node; 215 | 216 | if (element.ownerDocument.IsNotNull()) 217 | { 218 | Element[] elements = element.GetAllElements(); 219 | 220 | elements.Each((index, item) => 221 | { 222 | item.Index = this.ownerDocument.all.Count + index; 223 | }); 224 | 225 | this.ownerDocument.all.AddRange(elements); 226 | } 227 | else 228 | { 229 | element.Index = this.ownerDocument.all.Count; 230 | 231 | this.ownerDocument.all.Add(element); 232 | } 233 | } 234 | } 235 | 236 | /// 237 | /// 获取文档的子节点插入索引 238 | /// 239 | /// 240 | protected override int GetAppendIndex() 241 | { 242 | return this.End + 1; 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Document/DocumentContent.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Log; 4 | using Less.Text; 5 | using System; 6 | using System.Text; 7 | using System.Linq; 8 | using System.Diagnostics; 9 | 10 | namespace Less.Html 11 | { 12 | /// 13 | /// 文档内容 14 | /// 15 | internal class DocumentContent 16 | { 17 | private StringBuilder ValueBuilder 18 | { 19 | get; 20 | set; 21 | } 22 | 23 | private string ValueCache 24 | { 25 | get; 26 | set; 27 | } 28 | 29 | private bool SelfChecking 30 | { 31 | get; 32 | set; 33 | } 34 | 35 | private string Value 36 | { 37 | get 38 | { 39 | if (this.ValueBuilder.IsNotNull()) 40 | { 41 | this.ValueCache = this.ValueBuilder.ToString(); 42 | 43 | #if DEBUG 44 | if (!SelfChecking) 45 | { 46 | AppLog.Write(new 47 | { 48 | this.ValueCache, 49 | StackTrace = new StackTrace().GetFrames().Select(i => i.GetMethod().Name) 50 | }, false); 51 | } 52 | #endif 53 | 54 | this.ValueBuilder = null; 55 | } 56 | 57 | return this.ValueCache; 58 | } 59 | set 60 | { 61 | this.ValueCache = value; 62 | } 63 | } 64 | 65 | private DocumentContent(string value) 66 | { 67 | this.Value = value; 68 | } 69 | 70 | /// 71 | /// 文档长度 72 | /// 73 | public int Length 74 | { 75 | get 76 | { 77 | if (this.ValueBuilder.IsNotNull()) 78 | { 79 | return this.ValueBuilder.Length; 80 | } 81 | else 82 | { 83 | return this.Value.Length; 84 | } 85 | } 86 | } 87 | 88 | /// 89 | /// 获取指定索引的字符 90 | /// 91 | /// 指定索引 92 | /// 93 | public char this[int index] 94 | { 95 | get 96 | { 97 | if (this.ValueBuilder.IsNotNull()) 98 | { 99 | return this.ValueBuilder[index]; 100 | } 101 | else 102 | { 103 | return this.Value[index]; 104 | } 105 | } 106 | } 107 | 108 | /// 109 | /// 从 string 到 DocumentContent 的隐式转换 110 | /// 111 | /// 112 | /// 113 | public static implicit operator DocumentContent(string value) 114 | { 115 | return value.IsNotNull() ? new DocumentContent(value) : null; 116 | } 117 | 118 | /// 119 | /// 从 DocumentContent 到 string 的隐式转换 120 | /// 121 | /// 122 | /// 123 | public static implicit operator string(DocumentContent value) 124 | { 125 | return value.IsNotNull() ? value.ToString() : null; 126 | } 127 | 128 | /// 129 | /// 输出字面量 130 | /// 131 | /// 132 | public override string ToString() 133 | { 134 | return this.Value; 135 | } 136 | 137 | /// 138 | /// 在自检状态下执行 139 | /// 140 | /// 在自检状态下执行的方法 141 | public void ExecInSelfCheck(Action action) 142 | { 143 | this.SelfChecking = true; 144 | 145 | action(); 146 | 147 | this.SelfChecking = false; 148 | } 149 | 150 | /// 151 | /// 将指定范围的字符从此实例中移除 152 | /// 153 | /// 起始索引 154 | /// 长度 155 | public void Remove(int startIndex, int count) 156 | { 157 | this.GetValueBuilder().Remove(startIndex, count); 158 | } 159 | 160 | /// 161 | /// 将字符串插入到此实例中的指定字符位置 162 | /// 163 | /// 起始索引 164 | /// 插入值 165 | public void Insert(int startIndex, string value) 166 | { 167 | this.GetValueBuilder().Insert(startIndex, value); 168 | } 169 | 170 | /// 171 | /// 获取引用此实例地址的子字符串 172 | /// 173 | /// 起始索引 174 | /// 长度 175 | /// 返回引用此实例地址的子字符串 176 | public string SubstringUnsafe(int startIndex, int length) 177 | { 178 | if (this.ValueBuilder.IsNotNull()) 179 | { 180 | char[] dest = new char[length]; 181 | 182 | this.ValueBuilder.CopyTo(startIndex, dest, 0, length); 183 | 184 | return new string(dest); 185 | } 186 | else 187 | { 188 | return this.ValueCache.SubstringUnsafe(startIndex, length); 189 | } 190 | } 191 | 192 | private StringBuilder GetValueBuilder() 193 | { 194 | if (this.ValueBuilder.IsNull()) 195 | { 196 | this.ValueBuilder = new StringBuilder(this.Value); 197 | } 198 | 199 | return this.ValueBuilder; 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Element/Indexes/IndexBase.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Less.Html 6 | { 7 | internal abstract class IndexBase 8 | { 9 | protected void Insert(List list, Element element) 10 | { 11 | if (list.Count > 0) 12 | { 13 | int end = list.Count - 1; 14 | 15 | for (int i = end; i >= 0; i--) 16 | { 17 | if (element.Index > list[i].Index) 18 | { 19 | if (i == end) 20 | { 21 | list.Add(element); 22 | } 23 | else 24 | { 25 | list.Insert(i + 1, element); 26 | } 27 | 28 | break; 29 | } 30 | else 31 | { 32 | if (i == 0) 33 | { 34 | list.Insert(0, element); 35 | } 36 | } 37 | } 38 | } 39 | else 40 | { 41 | list.Add(element); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Element/Indexes/IndexOnClass.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 元素 class 索引 9 | /// 10 | internal class IndexOnClass : IndexBase 11 | { 12 | private Dictionary> Dictionary 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | /// 19 | /// 初始化 20 | /// 21 | internal IndexOnClass() 22 | { 23 | this.Dictionary = new Dictionary>(); 24 | } 25 | 26 | internal void Remove(string className, Element element) 27 | { 28 | List list; 29 | 30 | if (this.Dictionary.TryGetValue(className, out list)) 31 | { 32 | list.Remove(element); 33 | } 34 | } 35 | 36 | internal Element[] Get(string className) 37 | { 38 | List list; 39 | 40 | if (this.Dictionary.TryGetValue(className, out list)) 41 | { 42 | return list.ToArray(); 43 | } 44 | else 45 | { 46 | return new Element[0]; 47 | } 48 | } 49 | 50 | internal void Add(string className, Element element) 51 | { 52 | List list; 53 | 54 | if (this.Dictionary.TryGetValue(className, out list)) 55 | { 56 | this.Insert(list, element); 57 | } 58 | else 59 | { 60 | list = new List(); 61 | 62 | list.Add(element); 63 | 64 | this.Dictionary.Add(className, list); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Element/Indexes/IndexOnId.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 元素 class 索引 9 | /// 10 | internal class IndexOnId 11 | { 12 | private Dictionary Dictionary 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | /// 19 | /// 初始化 20 | /// 21 | internal IndexOnId() 22 | { 23 | this.Dictionary = new Dictionary(); 24 | } 25 | 26 | internal void Remove(string id) 27 | { 28 | this.Dictionary.Remove(id); 29 | } 30 | 31 | internal Element Get(string id) 32 | { 33 | Element element; 34 | 35 | if (this.Dictionary.TryGetValue(id, out element)) 36 | { 37 | return element; 38 | } 39 | else 40 | { 41 | return null; 42 | } 43 | } 44 | 45 | internal void Add(string id, Element element) 46 | { 47 | if (id.IsNotNull()) 48 | { 49 | if (!this.Dictionary.ContainsKey(id)) 50 | { 51 | this.Dictionary.Add(id, element); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Element/Indexes/IndexOnName.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 元素 class 索引 9 | /// 10 | internal class IndexOnName : IndexBase 11 | { 12 | private Dictionary> Dictionary 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | /// 19 | /// 初始化 20 | /// 21 | internal IndexOnName() 22 | { 23 | this.Dictionary = new Dictionary>(); 24 | } 25 | 26 | internal void Remove(string name, Element element) 27 | { 28 | List list; 29 | 30 | if (this.Dictionary.TryGetValue(name, out list)) 31 | { 32 | list.Remove(element); 33 | } 34 | } 35 | 36 | internal Element[] Get(string name) 37 | { 38 | List list; 39 | 40 | if (this.Dictionary.TryGetValue(name, out list)) 41 | { 42 | return list.ToArray(); 43 | } 44 | else 45 | { 46 | return new Element[0]; 47 | } 48 | } 49 | 50 | internal void Add(string name, Element element) 51 | { 52 | if (name.IsNotNull()) 53 | { 54 | List list; 55 | 56 | if (this.Dictionary.TryGetValue(name, out list)) 57 | { 58 | this.Insert(list, element); 59 | } 60 | else 61 | { 62 | list = new List(); 63 | 64 | list.Add(element); 65 | 66 | this.Dictionary.Add(name, list); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Element/Indexes/IndexOnTagName.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | using System; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 元素 class 索引 10 | /// 11 | internal class IndexOnTagName : IndexBase 12 | { 13 | private Dictionary> Dictionary 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | /// 20 | /// 初始化 21 | /// 22 | internal IndexOnTagName() 23 | { 24 | this.Dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); 25 | } 26 | 27 | internal void Remove(string tagName, Element element) 28 | { 29 | List list; 30 | 31 | if (this.Dictionary.TryGetValue(tagName, out list)) 32 | { 33 | list.Remove(element); 34 | } 35 | } 36 | 37 | internal Element[] Get(string tagName) 38 | { 39 | List list; 40 | 41 | if (this.Dictionary.TryGetValue(tagName, out list)) 42 | { 43 | return list.ToArray(); 44 | } 45 | else 46 | { 47 | return new Element[0]; 48 | } 49 | } 50 | 51 | internal void Add(string tagName, Element element) 52 | { 53 | List list; 54 | 55 | if (this.Dictionary.TryGetValue(tagName, out list)) 56 | { 57 | this.Insert(list, element); 58 | } 59 | else 60 | { 61 | list = new List(); 62 | 63 | list.Add(element); 64 | 65 | this.Dictionary.Add(tagName, list); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Less.Html/Nodes/NamedNodeMap.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using Less.Text; 6 | using Less.Collection; 7 | 8 | namespace Less.Html 9 | { 10 | /// 11 | /// 节点集合 12 | /// 命名节点映射表 13 | /// 14 | /// 15 | public class NamedNodeMap : IEnumerable where T : Node 16 | { 17 | private List Value 18 | { 19 | get; 20 | set; 21 | } 22 | 23 | internal Node Reference 24 | { 25 | get; 26 | set; 27 | } 28 | 29 | internal int Count 30 | { 31 | get 32 | { 33 | return this.Value.Count; 34 | } 35 | } 36 | 37 | /// 38 | /// 指定位置的节点 39 | /// 40 | /// 41 | /// 42 | public T this[int index] 43 | { 44 | get { return this.Value[index]; } 45 | set { this.Value[index] = value; } 46 | } 47 | 48 | /// 49 | /// 指定名称的节点 50 | /// 51 | /// 52 | /// 53 | public T this[string name] 54 | { 55 | get 56 | { 57 | foreach (T i in this.Value) 58 | { 59 | if (i.nodeName.IsNotNull() && i.nodeName.CompareIgnoreCase(name)) 60 | { 61 | return i; 62 | } 63 | } 64 | 65 | return null; 66 | } 67 | set 68 | { 69 | this[name] = value; 70 | } 71 | } 72 | 73 | internal NamedNodeMap(Node reference) 74 | { 75 | this.Value = new List(); 76 | 77 | this.Reference = reference; 78 | } 79 | 80 | /// 81 | /// 获取节点 82 | /// 83 | /// 84 | /// 85 | public T getNamedItem(string name) 86 | { 87 | return this[name]; 88 | } 89 | 90 | /// 91 | /// 移除指定的节点 92 | /// 93 | /// 94 | /// 95 | public T removeNamedItem(string name) 96 | { 97 | T node = this[name]; 98 | 99 | if (node.IsNotNull()) 100 | { 101 | node.OnRemoveNamedItem(); 102 | 103 | node.OnRemoveFromNamedNodeMap(); 104 | } 105 | 106 | return node; 107 | } 108 | 109 | /// 110 | /// 设置节点 111 | /// 112 | /// 113 | /// 114 | public T setNamedItem(T item) 115 | { 116 | //有些属性的 nodeName 是空引用,比如 !DOCTYPE 里面的属性 117 | T node = item.nodeName.IsNotNull() ? this[item.nodeName] : null; 118 | 119 | if (node.IsNull()) 120 | { 121 | item.OnAddNamedItem(this.Reference); 122 | 123 | return null; 124 | } 125 | else 126 | { 127 | T original = node; 128 | 129 | node.OnRemoveFromNamedNodeMap(); 130 | 131 | item.OnChangeNamedItem(this.Reference, node); 132 | 133 | item.OnAddToNamedNodeMap(); 134 | 135 | return original; 136 | } 137 | } 138 | 139 | /// 140 | /// 迭代器 141 | /// 142 | /// 143 | public IEnumerator GetEnumerator() 144 | { 145 | return this.Value.GetEnumerator(); 146 | } 147 | 148 | IEnumerator IEnumerable.GetEnumerator() 149 | { 150 | return this.Value.GetEnumerator(); 151 | } 152 | 153 | internal void Remove(T item) 154 | { 155 | this.Value.RemoveAt(item.ChildIndex); 156 | 157 | foreach (Node i in this.Value.GetEnumerator(item.ChildIndex)) 158 | { 159 | i.ChildIndex = i.ChildIndex - 1; 160 | } 161 | } 162 | 163 | internal void Insert(int index, T item) 164 | { 165 | this.Value.Insert(index, item); 166 | 167 | item.ChildIndex = index; 168 | 169 | foreach (Node i in this.Value.GetEnumerator(index + 1)) 170 | { 171 | i.ChildIndex += 1; 172 | } 173 | 174 | item.OnAddToNamedNodeMap(); 175 | } 176 | 177 | internal void Add(T item) 178 | { 179 | item.ChildIndex = this.Value.Count; 180 | 181 | this.Value.Add(item); 182 | 183 | item.OnAddToNamedNodeMap(); 184 | } 185 | 186 | internal void AddItem(T item) 187 | { 188 | item.ChildIndex = this.Value.Count; 189 | 190 | this.Value.Add(item); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Less.Html/Nodes/SelfCheckingException.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 自检错误 9 | /// 10 | public class SelfCheckingException : Exception 11 | { 12 | /// 13 | /// 初始化 14 | /// 15 | public SelfCheckingException() : base("自检错误") 16 | { 17 | // 18 | } 19 | 20 | /// 21 | /// 初始化 22 | /// 23 | /// 24 | public SelfCheckingException(Exception innerException) : base("自检错误", innerException) 25 | { 26 | // 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Less.Html/Nodes/Text.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 文本 9 | /// 10 | public class Text : Node 11 | { 12 | /// 13 | /// 元素名称 14 | /// 15 | public override string nodeName 16 | { 17 | get 18 | { 19 | return "#text".ToUpper(); 20 | } 21 | } 22 | 23 | /// 24 | /// 节点值 25 | /// 26 | public override string nodeValue 27 | { 28 | get 29 | { 30 | int length = this.End - this.Begin + 1; 31 | 32 | string content = this.ownerDocument.Content.SubstringUnsafe(this.Begin, length); 33 | 34 | return this.Decode(content); 35 | } 36 | } 37 | 38 | internal Text(int begin, int end) : base(begin, end) 39 | { 40 | // 41 | } 42 | 43 | /// 44 | /// 克隆节点 45 | /// 46 | /// 47 | /// 48 | public override Node cloneNode(bool deep) 49 | { 50 | return this.ownerDocument.Parse(this.Content).firstChild; 51 | } 52 | 53 | internal override Node Clone(Node parent) 54 | { 55 | Text clone = new Text(this.Begin, this.End); 56 | 57 | clone.parentNode = parent; 58 | 59 | clone.ChildIndex = this.ChildIndex; 60 | 61 | parent.ChildNodeList.Add(clone); 62 | 63 | clone.Index = this.Index; 64 | 65 | parent.ownerDocument.AllNodes.Add(clone); 66 | 67 | clone.ownerDocument = parent.ownerDocument; 68 | 69 | return clone; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Less.Html/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // 有关程序集的一般信息由以下 5 | // 控制。更改这些特性值可修改 6 | // 与程序集关联的信息。 7 | [assembly: AssemblyTitle("Less.Html")] 8 | [assembly: AssemblyDescription("一个犀利的 HTML 解析器")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Less.Html")] 12 | [assembly: AssemblyCopyright("Copyright © 2017 Shao Zhuang")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | //将 ComVisible 设置为 false 将使此程序集中的类型 17 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, 18 | //请将此类型的 ComVisible 特性设置为 true。 19 | [assembly: ComVisible(false)] 20 | 21 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 22 | [assembly: Guid("5749ea1f-89f8-4029-96be-457170de431a")] 23 | 24 | // 程序集的版本信息由下列四个值组成: 25 | // 26 | // 主版本 27 | // 次版本 28 | // 生成号 29 | // 修订号 30 | // 31 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 32 | // 方法是按如下所示使用“*”: : 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.7.2.*")] 35 | [assembly: AssemblyFileVersion("1.7.2.1")] 36 | -------------------------------------------------------------------------------- /Less.Html/Query/ElementExtensions.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 元素扩展方法 10 | /// 11 | internal static class ElementExtensions 12 | { 13 | internal static Document GetOwnerDocument(this IEnumerable elements) 14 | { 15 | Document ownerDocument = null; 16 | 17 | foreach (Element i in elements) 18 | { 19 | if (ownerDocument.IsNull()) 20 | { 21 | ownerDocument = i.ownerDocument; 22 | } 23 | else 24 | { 25 | if (ownerDocument != i.ownerDocument) 26 | { 27 | return null; 28 | } 29 | } 30 | } 31 | 32 | return ownerDocument; 33 | } 34 | 35 | /// 36 | /// 获取子元素 37 | /// 38 | /// 39 | /// 40 | internal static IEnumerable GetChildElements(this IEnumerable elements) 41 | { 42 | return elements.SelectMany(i => i.ChildNodeList.GetElements()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Less.Html/Query/NodeExtensions.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 节点扩展方法 10 | /// 11 | internal static class NodeExtensions 12 | { 13 | /// 14 | /// 获取节点中的元素 15 | /// 16 | /// 17 | /// 18 | internal static IEnumerable GetElements(this IEnumerable nodes) 19 | { 20 | return nodes.Where(i => i is Element).Cast(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Less.Html/Query/QueryExtensions.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 查询器 9 | /// 10 | public partial class Query : IEnumerable 11 | { 12 | /// 13 | /// 获取元素的 id 属性 14 | /// 15 | /// 16 | public string id() 17 | { 18 | return this.attr("id"); 19 | } 20 | 21 | /// 22 | /// 设置元素的 id 属性 23 | /// 24 | /// 25 | /// 26 | public Query id(object value) 27 | { 28 | this.id(value.ToString()); 29 | 30 | return this; 31 | } 32 | 33 | /// 34 | /// 设置元素的 id 属性 35 | /// 36 | /// 37 | /// 38 | public Query id(string value) 39 | { 40 | this.attr("id", value); 41 | 42 | return this; 43 | } 44 | 45 | /// 46 | /// 获取元素的 selected 属性 47 | /// 48 | /// 49 | public string selected() 50 | { 51 | return this.attr("selected"); 52 | } 53 | 54 | /// 55 | /// 设置元素的 selected 属性 56 | /// 57 | /// 58 | /// 59 | public Query selected(string value) 60 | { 61 | this.attr("selected", value); 62 | 63 | return this; 64 | } 65 | 66 | /// 67 | /// 获取元素的 checked 属性 68 | /// 69 | /// 70 | public string chked() 71 | { 72 | return this.attr("checked"); 73 | } 74 | 75 | /// 76 | /// 设置元素的 checked 属性 77 | /// 78 | /// 79 | /// 80 | public Query chked(string value) 81 | { 82 | this.attr("checked", value); 83 | 84 | return this; 85 | } 86 | 87 | /// 88 | /// 获取元素的 title 属性 89 | /// 90 | /// 91 | public string title() 92 | { 93 | return this.attr("title"); 94 | } 95 | 96 | /// 97 | /// 设置元素的 title 属性 98 | /// 99 | /// 100 | /// 101 | public Query title(string value) 102 | { 103 | return this.attr("title", value); 104 | } 105 | 106 | /// 107 | /// 获取元素的 src 属性 108 | /// 109 | /// 110 | /// 选择器参数错误 111 | public string src() 112 | { 113 | return this.attr("src"); 114 | } 115 | 116 | /// 117 | /// 设置元素的 src 属性 118 | /// 119 | /// 120 | /// 121 | /// 选择器参数错误 122 | public Query src(string value) 123 | { 124 | return this.attr("src", value); 125 | } 126 | 127 | /// 128 | /// 获取元素的 href 属性 129 | /// 130 | /// 131 | /// 选择器参数错误 132 | public string href() 133 | { 134 | return this.attr("href"); 135 | } 136 | 137 | /// 138 | /// 设置元素的 href 属性 139 | /// 140 | /// 141 | /// 142 | /// 选择器参数错误 143 | public Query href(string value) 144 | { 145 | return this.attr("href", value); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Less.Html/Query/Selector.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using Less.Html.SelectorParser; 6 | using System.Linq; 7 | 8 | namespace Less.Html 9 | { 10 | /// 11 | /// 选择器委托 12 | /// 13 | /// 14 | /// 15 | public delegate Query Qfun(SelectorParam param); 16 | 17 | /// 18 | /// 选择器 19 | /// 20 | public class Selector 21 | { 22 | internal Document Document 23 | { 24 | get; 25 | set; 26 | } 27 | 28 | internal SelectorParam Param 29 | { 30 | get; 31 | set; 32 | } 33 | 34 | internal List, IEnumerable>> ExtFilterList 35 | { 36 | get; 37 | private set; 38 | } 39 | 40 | private Selector(Document document, SelectorParam selectorParam) 41 | { 42 | this.Document = document; 43 | this.Param = selectorParam; 44 | 45 | this.ExtFilterList = new List, IEnumerable>>(); 46 | } 47 | 48 | /// 49 | /// 绑定文档 50 | /// 51 | /// 文档 52 | /// jQuery 风格的 css 选择器 53 | /// document 不能为 null 54 | public static Qfun Bind(Document document) 55 | { 56 | if (document.IsNull()) 57 | { 58 | throw new ArgumentNullException("document", "document 不能为 null"); 59 | } 60 | 61 | return selectorParam => new Query(new Selector(document, selectorParam)); 62 | } 63 | 64 | /// 65 | /// 重新绑定同一个文档 以取得一个新的查询器 66 | /// 67 | /// 68 | public Qfun Rebind() 69 | { 70 | return Selector.Bind(this.Document); 71 | } 72 | 73 | /// 74 | /// 选择元素 75 | /// 76 | /// 77 | internal IEnumerable Select() 78 | { 79 | if (this.Param.IsNotNull()) 80 | { 81 | if (this.Param.StringValue.IsNotNull()) 82 | { 83 | List nodes = this.Document.Parse(this.Param.StringValue).ChildNodeList; 84 | 85 | if (nodes.Any(i => i is Element)) 86 | { 87 | this.Param.NodesValue = nodes.ToArray(); 88 | 89 | this.Param.StringValue = null; 90 | } 91 | } 92 | 93 | IEnumerable selected; 94 | 95 | if (this.Param.NodesValue.IsNull()) 96 | { 97 | if (this.Param.StringValue.IsNull()) 98 | { 99 | selected = this.Param.QueryValue.Select(); 100 | } 101 | else 102 | { 103 | selected = this.Select(this.Document, this.Param.StringValue); 104 | } 105 | } 106 | else 107 | { 108 | IEnumerable source = this.Param.NodesValue.GetElements(); 109 | 110 | if (this.Param.StringValue.IsNull()) 111 | { 112 | selected = source; 113 | } 114 | else 115 | { 116 | Document document = source.GetOwnerDocument(); 117 | 118 | selected = this.Select(document, source, this.Param.StringValue); 119 | } 120 | } 121 | 122 | foreach (Func, IEnumerable> i in this.ExtFilterList) 123 | { 124 | selected = i(selected); 125 | } 126 | 127 | return selected; 128 | } 129 | else 130 | { 131 | return new Element[0]; 132 | } 133 | } 134 | 135 | /// 136 | /// 查询元素 137 | /// 138 | /// 139 | /// 140 | /// 141 | internal IEnumerable Select(Document document, string param) 142 | { 143 | return ParamParser.Parse(param).SelectMany(i => i.Eval(document)).Distinct().OrderBy(i => i.Index); 144 | } 145 | 146 | /// 147 | /// 查询元素 148 | /// 149 | /// 150 | /// 151 | /// 152 | /// 153 | internal IEnumerable Select(Document document, IEnumerable source, string param) 154 | { 155 | ElementFilter[] filters = ParamParser.Parse(param); 156 | 157 | if (filters.Length > 1) 158 | { 159 | return filters.SelectMany(i => i.Eval(document, source)).Distinct().OrderBy(i => i.Index); 160 | } 161 | else 162 | { 163 | return filters[0].Eval(document, source).ToArray(); 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Less.Html/Query/SelectorParam.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Collection; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 选择器参数 9 | /// 10 | public class SelectorParam 11 | { 12 | /// 13 | /// 节点值 14 | /// 15 | internal Node[] NodesValue 16 | { 17 | get; 18 | set; 19 | } 20 | 21 | /// 22 | /// 字符串值 23 | /// 24 | internal string StringValue 25 | { 26 | get; 27 | set; 28 | } 29 | 30 | /// 31 | /// 查询器值 32 | /// 33 | internal Query QueryValue 34 | { 35 | get; 36 | set; 37 | } 38 | 39 | /// 40 | /// 初始化 41 | /// 42 | /// 43 | protected SelectorParam(string value) 44 | { 45 | this.StringValue = value.IsNull(""); 46 | } 47 | 48 | /// 49 | /// 初始化 50 | /// 51 | /// 52 | protected SelectorParam(Node[] nodes) 53 | { 54 | this.NodesValue = nodes; 55 | } 56 | 57 | /// 58 | /// 初始化 59 | /// 60 | /// 61 | protected SelectorParam(Query query) 62 | { 63 | this.QueryValue = query; 64 | } 65 | 66 | /// 67 | /// 初始化 68 | /// 69 | protected SelectorParam() 70 | { 71 | // 72 | } 73 | 74 | /// 75 | /// 从 string 到 SelectorParam 的隐式转换 76 | /// 77 | /// 78 | public static implicit operator SelectorParam(string value) 79 | { 80 | return new SelectorParam(value); 81 | } 82 | 83 | /// 84 | /// 从 Node[] 到 SelectorParam 的隐式转换 85 | /// 86 | /// 87 | public static implicit operator SelectorParam(Node[] nodes) 88 | { 89 | return new SelectorParam(nodes); 90 | } 91 | 92 | /// 93 | /// 从 Node 到 SelectorParam 的隐式转换 94 | /// 95 | /// 96 | public static implicit operator SelectorParam(Node node) 97 | { 98 | return node.ConstructArray(); 99 | } 100 | 101 | /// 102 | /// 从 Query 到 SelectorParam 的隐式转换 103 | /// 104 | /// 105 | public static implicit operator SelectorParam(Query query) 106 | { 107 | return new SelectorParam(query); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/AttrSelectorReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | using Less.Text; 5 | 6 | namespace Less.Html.SelectorParser 7 | { 8 | internal class AttrSelectorReader : ReaderBase 9 | { 10 | private static Regex Pattern 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | private static Regex ValuePattern 17 | { 18 | get; 19 | set; 20 | } 21 | 22 | static AttrSelectorReader() 23 | { 24 | AttrSelectorReader.Pattern = @" 25 | (?.*?)='(?\S*?)'\]| 26 | (?.*?)=""(?\S*?)""\]| 27 | (?.*?)=(?\S*?)\]| 28 | (?.*?)\] 29 | ".ToRegex( 30 | RegexOptions.IgnorePatternWhitespace | 31 | RegexOptions.Multiline | 32 | RegexOptions.Compiled | 33 | RegexOptions.ExplicitCapture); 34 | } 35 | 36 | internal override ReaderBase Read() 37 | { 38 | Match match = AttrSelectorReader.Pattern.Match(this.Param, this.Position); 39 | 40 | if (match.Success) 41 | { 42 | this.Ascend(match); 43 | 44 | bool next = !this.PreSpace; 45 | 46 | string name = match.GetValue("name"); 47 | 48 | string value = null; 49 | 50 | Group group = match.Groups["value"]; 51 | 52 | if (group.Success) 53 | { 54 | value = group.Value; 55 | } 56 | 57 | this.AddFilter(new FilterByAttr(name, value), next); 58 | 59 | return this.Pass(); 60 | } 61 | 62 | throw new SelectorParamException(this.Position, this.Param[this.Position].ToString()); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Less.Html/SelectorParser/Context.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Less.Html.SelectorParser 6 | { 7 | internal class Context 8 | { 9 | internal string Param 10 | { 11 | get; 12 | private set; 13 | } 14 | 15 | internal int Position 16 | { 17 | get; 18 | set; 19 | } 20 | 21 | internal string CurrentSymbol 22 | { 23 | get; 24 | set; 25 | } 26 | 27 | internal bool PreSpace 28 | { 29 | get; 30 | set; 31 | } 32 | 33 | internal List FilterList 34 | { 35 | get; 36 | private set; 37 | } 38 | 39 | internal ElementFilter CurrentFilter 40 | { 41 | get; 42 | set; 43 | } 44 | 45 | internal Context(string param) 46 | { 47 | this.Param = param; 48 | 49 | this.FilterList = new List(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/ElementFilter.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Less.Html.SelectorParser 6 | { 7 | internal abstract class ElementFilter 8 | { 9 | internal ElementFilter Next 10 | { 11 | get; 12 | set; 13 | } 14 | 15 | internal ElementFilter Child 16 | { 17 | get; 18 | set; 19 | } 20 | 21 | internal IEnumerable Eval(Document document) 22 | { 23 | IEnumerable elements = this.EvalThis(document); 24 | 25 | return this.EvalOthers(document, elements); 26 | } 27 | 28 | internal IEnumerable Eval(Document document, IEnumerable source) 29 | { 30 | IEnumerable elements = this.EvalThis(document, source); 31 | 32 | return this.EvalOthers(document, elements); 33 | } 34 | 35 | protected abstract IEnumerable EvalThis(Document document); 36 | 37 | protected abstract IEnumerable EvalThis(Document document, IEnumerable source); 38 | 39 | private IEnumerable EvalOthers(Document document, IEnumerable elements) 40 | { 41 | if (this.Next.IsNotNull()) 42 | { 43 | elements = this.Next.Eval(document, elements); 44 | } 45 | else 46 | { 47 | if (this.Child.IsNotNull()) 48 | { 49 | elements = this.Child.Eval(document, elements.GetChildElements()); 50 | } 51 | } 52 | 53 | return elements; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/Filters/FilterByAll.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Less.Html.SelectorParser 7 | { 8 | internal class FilterByAll : ElementFilter 9 | { 10 | protected override IEnumerable EvalThis(Document document) 11 | { 12 | return document.all; 13 | } 14 | 15 | protected override IEnumerable EvalThis(Document document, IEnumerable source) 16 | { 17 | if (document.IsNotNull()) 18 | { 19 | return source.SelectMany(i => i.EnumerateAllElements()); 20 | } 21 | else 22 | { 23 | return source.GetChildElements(); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/Filters/FilterByAttr.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using Less.Text; 6 | 7 | namespace Less.Html.SelectorParser 8 | { 9 | internal class FilterByAttr : ElementFilter 10 | { 11 | internal string Name 12 | { 13 | get; 14 | private set; 15 | } 16 | 17 | internal string Value 18 | { 19 | get; 20 | private set; 21 | } 22 | 23 | internal FilterByAttr(string name, string value) 24 | { 25 | this.Name = name; 26 | this.Value = value; 27 | } 28 | 29 | protected override IEnumerable EvalThis(Document document) 30 | { 31 | if (this.Name.CompareIgnoreCase("name")) 32 | { 33 | if (this.Value.IsNotNull()) 34 | { 35 | return document.getElementsByName(this.Value); 36 | } 37 | } 38 | 39 | return this.GetElementsByAttr(document.ChildNodeList.GetElements()); 40 | } 41 | 42 | protected override IEnumerable EvalThis(Document document, IEnumerable source) 43 | { 44 | if (document.IsNotNull()) 45 | { 46 | if (this.Name.CompareIgnoreCase("name")) 47 | { 48 | if (this.Value.IsNotNull()) 49 | { 50 | Element[] elements = document.getElementsByName(this.Value); 51 | 52 | return source.SelectMany(i => elements.Where(j => i.Contains(j))); 53 | } 54 | } 55 | } 56 | 57 | return this.GetElementsByAttr(source); 58 | } 59 | 60 | private IEnumerable GetElementsByAttr(IEnumerable source) 61 | { 62 | return source.SelectMany(i => i.EnumerateAllElements().Where(j => 63 | { 64 | if (this.Value.IsNull()) 65 | { 66 | return j.attributes[this.Name].IsNotNull(); 67 | } 68 | else 69 | { 70 | Attr attr = j.attributes[this.Name]; 71 | 72 | if (attr.IsNull()) 73 | { 74 | return false; 75 | } 76 | else 77 | { 78 | return attr.value == this.Value; 79 | } 80 | } 81 | })); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/Filters/FilterByClass.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Linq; 4 | using Less.Text; 5 | using System.Collections.Generic; 6 | 7 | namespace Less.Html.SelectorParser 8 | { 9 | internal class FilterByClass : ElementFilter 10 | { 11 | internal string Class 12 | { 13 | get; 14 | private set; 15 | } 16 | 17 | internal FilterByClass(string className) 18 | { 19 | this.Class = className; 20 | } 21 | 22 | protected override IEnumerable EvalThis(Document document) 23 | { 24 | return document.all.GetElementsByClassName(this.Class); 25 | } 26 | 27 | protected override IEnumerable EvalThis(Document document, IEnumerable source) 28 | { 29 | if (document.IsNotNull()) 30 | { 31 | Element[] elements = document.all.GetElementsByClassName(this.Class); 32 | 33 | return source.SelectMany(i => elements.Where(j => i.Contains(j))); 34 | } 35 | else 36 | { 37 | return source.SelectMany(i => i.EnumerateAllElements().Where( 38 | j => 39 | j.className.IsNotNull() && 40 | j.className.SplitByWhiteSpace().Any(k => k == this.Class))); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/Filters/FilterById.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using Less.Collection; 6 | 7 | namespace Less.Html.SelectorParser 8 | { 9 | internal class FilterById : ElementFilter 10 | { 11 | internal string Id 12 | { 13 | get; 14 | private set; 15 | } 16 | 17 | internal FilterById(string id) 18 | { 19 | this.Id = id; 20 | } 21 | 22 | protected override IEnumerable EvalThis(Document document) 23 | { 24 | Element element = document.getElementById(this.Id); 25 | 26 | if (element.IsNotNull()) 27 | { 28 | return element.ConstructArray(); 29 | } 30 | else 31 | { 32 | return new Element[0]; 33 | } 34 | } 35 | 36 | protected override IEnumerable EvalThis(Document document, IEnumerable source) 37 | { 38 | if (document.IsNotNull()) 39 | { 40 | Element element = document.getElementById(this.Id); 41 | 42 | if (element.IsNotNull()) 43 | { 44 | if (source.Any(i => i.Contains(element))) 45 | { 46 | return element.ConstructArray(); 47 | } 48 | } 49 | 50 | return new Element[0]; 51 | } 52 | else 53 | { 54 | return source.SelectMany(i => i.EnumerateAllElements().Where( 55 | j => 56 | j.id.IsNotNull() && j.id == this.Id)); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/Filters/FilterByOther.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using Less.Collection; 7 | using Less.Text; 8 | 9 | namespace Less.Html.SelectorParser 10 | { 11 | internal class FilterByOther : ElementFilter 12 | { 13 | private static Regex EqPattern 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | private static Regex GtPattern 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | private static Regex LtPattern 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | internal string Condition 32 | { 33 | get; 34 | private set; 35 | } 36 | 37 | internal int Index 38 | { 39 | get; 40 | private set; 41 | } 42 | 43 | static FilterByOther() 44 | { 45 | FilterByOther.EqPattern = @"eq\((?\d+)\)".ToRegex( 46 | RegexOptions.Compiled | 47 | RegexOptions.IgnoreCase); 48 | 49 | FilterByOther.GtPattern = @"gt\((?\d+)\)".ToRegex( 50 | RegexOptions.Compiled | 51 | RegexOptions.IgnoreCase); 52 | 53 | FilterByOther.LtPattern = @"lt\((?\d+)\)".ToRegex( 54 | RegexOptions.Compiled | 55 | RegexOptions.IgnoreCase); 56 | } 57 | 58 | internal FilterByOther(string condition, int index) 59 | { 60 | this.Condition = condition; 61 | this.Index = index; 62 | } 63 | 64 | protected override IEnumerable EvalThis(Document document) 65 | { 66 | return this.EvalThis(document, document.ChildNodeList.GetElements()); 67 | } 68 | 69 | protected override IEnumerable EvalThis(Document document, IEnumerable source) 70 | { 71 | if (this.Condition.CompareIgnoreCase("first")) 72 | { 73 | return source.Take(1); 74 | } 75 | else if (this.Condition.CompareIgnoreCase("last")) 76 | { 77 | Element last = source.LastOrDefault(); 78 | 79 | if (last.IsNull()) 80 | { 81 | return new Element[0]; 82 | } 83 | else 84 | { 85 | return last.ConstructArray(); 86 | } 87 | } 88 | else if (this.Condition.CompareIgnoreCase("checkbox")) 89 | { 90 | return source.SelectMany(i => i.EnumerateAllElements().Where( 91 | j => 92 | j.Name.CompareIgnoreCase("input") && 93 | j.getAttribute("type").CompareIgnoreCase("checkbox"))); 94 | } 95 | else 96 | { 97 | Match eq = FilterByOther.EqPattern.Match(this.Condition); 98 | 99 | if (eq.Success) 100 | { 101 | int? index = eq.GetValue("index").ToInt(); 102 | 103 | if (index.IsNotNull()) 104 | { 105 | return source.SelectMany(i => i.EnumerateAllElements()).Skip(index.Value).Take(1); 106 | } 107 | else 108 | { 109 | throw new SelectorParamException(this.Index, this.Condition); 110 | } 111 | } 112 | else 113 | { 114 | Match gt = FilterByOther.GtPattern.Match(this.Condition); 115 | 116 | if (gt.Success) 117 | { 118 | int? index = gt.GetValue("index").ToInt(); 119 | 120 | if (index.IsNotNull()) 121 | { 122 | return source.SelectMany(i => i.EnumerateAllElements()).Skip(index.Value + 1); 123 | } 124 | else 125 | { 126 | throw new SelectorParamException(this.Index, this.Condition); 127 | } 128 | } 129 | else 130 | { 131 | Match lt = FilterByOther.LtPattern.Match(this.Condition); 132 | 133 | if (lt.Success) 134 | { 135 | int? index = lt.GetValue("index").ToInt(); 136 | 137 | if (index.IsNotNull()) 138 | { 139 | return source.SelectMany(i => i.EnumerateAllElements()).Take(index.Value); 140 | } 141 | else 142 | { 143 | throw new SelectorParamException(this.Index, this.Condition); 144 | } 145 | } 146 | else 147 | { 148 | throw new SelectorParamException(this.Index, this.Condition); 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/Filters/FilterByTagName.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using Less.Text; 6 | 7 | namespace Less.Html.SelectorParser 8 | { 9 | internal class FilterByTagName : ElementFilter 10 | { 11 | internal string Name 12 | { 13 | get; 14 | private set; 15 | } 16 | 17 | internal FilterByTagName(string name) 18 | { 19 | this.Name = name; 20 | } 21 | 22 | protected override IEnumerable EvalThis(Document document) 23 | { 24 | return document.getElementsByTagName(this.Name); 25 | } 26 | 27 | protected override IEnumerable EvalThis(Document document, IEnumerable source) 28 | { 29 | if (document.IsNotNull()) 30 | { 31 | Element[] elements = document.getElementsByTagName(this.Name); 32 | 33 | return source.SelectMany(i => elements.Where(j => i.Contains(j))); 34 | } 35 | else 36 | { 37 | return source.SelectMany(i => i.EnumerateAllElements().Where( 38 | j => 39 | j.nodeName.CompareIgnoreCase(this.Name))); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/ParamParser.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Less.Html.SelectorParser 7 | { 8 | internal static class ParamParser 9 | { 10 | private static Dictionary Cache 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | static ParamParser() 17 | { 18 | ParamParser.Cache = new Dictionary(); 19 | } 20 | 21 | internal static ElementFilter[] Parse(string param) 22 | { 23 | ElementFilter[] filters; 24 | 25 | if (ParamParser.Cache.TryGetValue(param, out filters)) 26 | { 27 | return filters; 28 | } 29 | else 30 | { 31 | ReaderBase reader = new SelectorReader(); 32 | 33 | Context context = new Context(param); 34 | 35 | reader.Context = context; 36 | 37 | while (true) 38 | { 39 | reader = reader.Read(); 40 | 41 | if (reader.IsNull()) 42 | { 43 | break; 44 | } 45 | } 46 | 47 | filters = context.FilterList.ToArray(); 48 | 49 | try 50 | { 51 | ParamParser.Cache.Add(param, filters); 52 | } 53 | catch (ArgumentException) 54 | { 55 | // 56 | } 57 | 58 | return filters; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/ReaderBase.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections.Generic; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Less.Html.SelectorParser 7 | { 8 | internal abstract class ReaderBase 9 | { 10 | internal Context Context 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | internal int Position 17 | { 18 | get 19 | { 20 | return this.Context.Position; 21 | } 22 | private set 23 | { 24 | this.Context.Position = value; 25 | } 26 | } 27 | 28 | internal string Param 29 | { 30 | get 31 | { 32 | return this.Context.Param; 33 | } 34 | } 35 | 36 | internal string CurrentSymbol 37 | { 38 | get 39 | { 40 | return this.Context.CurrentSymbol; 41 | } 42 | set 43 | { 44 | this.Context.CurrentSymbol = value; 45 | } 46 | } 47 | 48 | internal bool PreSpace 49 | { 50 | get 51 | { 52 | return this.Context.PreSpace; 53 | } 54 | set 55 | { 56 | this.Context.PreSpace = value; 57 | } 58 | } 59 | 60 | internal List FilterList 61 | { 62 | get 63 | { 64 | return this.Context.FilterList; 65 | } 66 | } 67 | 68 | internal ElementFilter CurrentFilter 69 | { 70 | get 71 | { 72 | return this.Context.CurrentFilter; 73 | } 74 | set 75 | { 76 | this.Context.CurrentFilter = value; 77 | } 78 | } 79 | 80 | internal void SetNext(ElementFilter next) 81 | { 82 | if (this.CurrentFilter.IsNull()) 83 | { 84 | this.FilterList.Add(next); 85 | } 86 | else 87 | { 88 | this.CurrentFilter.Next = next; 89 | } 90 | 91 | this.CurrentFilter = next; 92 | } 93 | 94 | internal void SetChild(ElementFilter child) 95 | { 96 | if (this.CurrentFilter.IsNull()) 97 | { 98 | this.FilterList.Add(child); 99 | } 100 | else 101 | { 102 | this.CurrentFilter.Child = child; 103 | } 104 | 105 | this.CurrentFilter = child; 106 | } 107 | 108 | internal abstract ReaderBase Read(); 109 | 110 | protected void Ascend(Match match) 111 | { 112 | this.Position = match.Index + match.Length; 113 | } 114 | 115 | protected T Pass() where T : ReaderBase, new() 116 | { 117 | T t = new T(); 118 | 119 | t.Context = this.Context; 120 | 121 | return t; 122 | } 123 | 124 | protected void AddFilter(ElementFilter filter, bool next) 125 | { 126 | if (next) 127 | { 128 | this.SetNext(filter); 129 | } 130 | else 131 | { 132 | this.SetChild(filter); 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/SelectorParamException.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System; 4 | using Less.Text; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 选择器参数错误 10 | /// 11 | public class SelectorParamException : Exception 12 | { 13 | /// 14 | /// 初始化 15 | /// 16 | /// 17 | /// 18 | public SelectorParamException(int index, string near) : 19 | base("选择器参数错误,在位置{0},“{1}”附近".FormatString(index, near)) 20 | { 21 | // 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Less.Html/SelectorParser/SelectorReader.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | using Less.Text; 5 | 6 | namespace Less.Html.SelectorParser 7 | { 8 | internal class SelectorReader : ReaderBase 9 | { 10 | private static Regex Pattern 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | static SelectorReader() 17 | { 18 | SelectorReader.Pattern = @" 19 | (?\s*)(?[\.#:\[,*])| 20 | (?\s*)(?[^\.#:\[,*\s]+) 21 | ".ToRegex( 22 | RegexOptions.IgnorePatternWhitespace | 23 | RegexOptions.Multiline | 24 | RegexOptions.Compiled | 25 | RegexOptions.ExplicitCapture); 26 | } 27 | 28 | internal override ReaderBase Read() 29 | { 30 | Match match = SelectorReader.Pattern.Match(this.Param, this.Position); 31 | 32 | if (match.Success) 33 | { 34 | this.Ascend(match); 35 | 36 | Group symbol = match.Groups["symbol"]; 37 | Group space = match.Groups["space"]; 38 | 39 | bool hasCurrentSymbol = this.CurrentSymbol.IsNotNull(); 40 | 41 | if (symbol.Success) 42 | { 43 | this.PreSpace = space.Length > 0; 44 | 45 | if (hasCurrentSymbol) 46 | { 47 | throw new SelectorParamException(symbol.Index, symbol.Value); 48 | } 49 | 50 | switch (symbol.Value) 51 | { 52 | case ",": 53 | if (this.CurrentFilter.IsNull()) 54 | { 55 | throw new SelectorParamException(symbol.Index, symbol.Value); 56 | } 57 | 58 | this.CurrentSymbol = null; 59 | this.CurrentFilter = null; 60 | 61 | break; 62 | case "*": 63 | if (this.CurrentFilter.IsNotNull() && !this.PreSpace) 64 | { 65 | throw new SelectorParamException(symbol.Index, symbol.Value); 66 | } 67 | 68 | this.AddFilter(new FilterByAll(), false); 69 | 70 | break; 71 | case "[": 72 | return this.Pass(); 73 | default: 74 | this.CurrentSymbol = symbol.Value; 75 | 76 | break; 77 | } 78 | } 79 | else 80 | { 81 | Group name = match.Groups["name"]; 82 | 83 | if (hasCurrentSymbol) 84 | { 85 | if (space.Length > 0) 86 | { 87 | throw new SelectorParamException(match.Index, match.Value); 88 | } 89 | 90 | bool next = !this.PreSpace; 91 | 92 | switch (this.CurrentSymbol) 93 | { 94 | case ".": 95 | this.AddFilter(new FilterByClass(name.Value), next); 96 | break; 97 | case "#": 98 | this.AddFilter(new FilterById(name.Value), next); 99 | break; 100 | case ":": 101 | this.AddFilter(new FilterByOther(name.Value, match.Index), next); 102 | break; 103 | default: 104 | throw new SelectorParamException(match.Index - this.CurrentSymbol.Length, this.CurrentSymbol); 105 | } 106 | } 107 | else 108 | { 109 | this.AddFilter(new FilterByTagName(name.Value), false); 110 | } 111 | 112 | this.CurrentSymbol = null; 113 | } 114 | 115 | return this.Pass(); 116 | } 117 | 118 | return null; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /Less.Html/StyleParser.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// style 解析器 10 | /// 11 | public static class StyleParser 12 | { 13 | private static Regex Pattern 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | static StyleParser() 20 | { 21 | StyleParser.Pattern = @"(?[\w-]+):\s*(?.+?)\s*(;|$)".ToRegex( 22 | RegexOptions.Singleline | 23 | RegexOptions.Compiled | 24 | RegexOptions.ExplicitCapture); 25 | } 26 | 27 | /// 28 | /// 解析 29 | /// 30 | /// 31 | /// 32 | public static Style Parse(string content) 33 | { 34 | Css css = new Css(content); 35 | 36 | Style style = new Style(css, 0); 37 | 38 | int position = 0; 39 | 40 | Match match = StyleParser.Pattern.Match(content, position); 41 | 42 | while (match.Success) 43 | { 44 | Group name = match.Groups["name"]; 45 | Group value = match.Groups["value"]; 46 | 47 | Property property = new Property(css, 48 | name.Index, 49 | name.Index + name.Length - 1, 50 | value.Index, 51 | value.Index + value.Length - 1, 52 | match.Index + match.Length - 1); 53 | 54 | style.Add(property); 55 | 56 | position = match.Index + match.Length; 57 | 58 | match = StyleParser.Pattern.Match(content, position); 59 | } 60 | 61 | style.End = css.Content.Length - 1; 62 | 63 | css.Styles.Add(style); 64 | 65 | return style; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Less.Html/Styles/Block.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 样式块 9 | /// 10 | public class Block : CssInfo 11 | { 12 | internal int NameEnd 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | /// 19 | /// 样式块名称 20 | /// 21 | public string Name 22 | { 23 | get 24 | { 25 | return this.OwnerCss.Content.SubstringUnsafe(this.Begin, this.NameEnd - this.Begin + 1); 26 | } 27 | } 28 | 29 | /// 30 | /// 样式集合 31 | /// 32 | public StyleCollection Styles 33 | { 34 | get; 35 | private set; 36 | } 37 | 38 | internal Block(Css ownerCss, int begin, int nameEnd) : base(ownerCss, begin) 39 | { 40 | this.Styles = new StyleCollection(); 41 | 42 | this.NameEnd = nameEnd; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Less.Html/Styles/BlockCollection.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 样式块集合 10 | /// 11 | public class BlockCollection : IEnumerable 12 | { 13 | private List List 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | /// 20 | /// 样式块计数 21 | /// 22 | public int Count 23 | { 24 | get 25 | { 26 | return this.List.Count; 27 | } 28 | } 29 | 30 | /// 31 | /// 获取指定索引的样式块 32 | /// 33 | /// 34 | /// 35 | public Block this[int index] 36 | { 37 | get 38 | { 39 | return this.List[index]; 40 | } 41 | } 42 | 43 | internal BlockCollection() 44 | { 45 | this.List = new List(); 46 | } 47 | 48 | internal void Add(Block block) 49 | { 50 | this.List.Add(block); 51 | } 52 | 53 | /// 54 | /// 获取迭代器 55 | /// 56 | /// 57 | public IEnumerator GetEnumerator() 58 | { 59 | return this.List.GetEnumerator(); 60 | } 61 | 62 | IEnumerator IEnumerable.GetEnumerator() 63 | { 64 | return this.List.GetEnumerator(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Less.Html/Styles/Css.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// css 文档 9 | /// 10 | public class Css 11 | { 12 | internal string Content 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | /// 19 | /// 样式集合 20 | /// 21 | public StyleCollection Styles 22 | { 23 | get; 24 | private set; 25 | } 26 | 27 | /// 28 | /// 样式块集合 29 | /// 30 | public BlockCollection Blocks 31 | { 32 | get; 33 | private set; 34 | } 35 | 36 | internal Css(string content) 37 | { 38 | this.Content = content; 39 | 40 | this.Styles = new StyleCollection(); 41 | this.Blocks = new BlockCollection(); 42 | } 43 | 44 | /// 45 | /// 替换文档内容 46 | /// 47 | /// 48 | /// 49 | /// 50 | internal void Replace(int index, int length, string replacement) 51 | { 52 | string before = this.Content.SubstringUnsafe(0, index); 53 | 54 | int afterIndex = index + length; 55 | 56 | string after = this.Content.SubstringUnsafe(afterIndex, this.Content.Length - afterIndex); 57 | 58 | this.Content = before + replacement + after; 59 | 60 | int offset = replacement.Length - length; 61 | 62 | this.Shift(index, offset); 63 | } 64 | 65 | /// 66 | /// 输出字面量 67 | /// 68 | /// 69 | public override string ToString() 70 | { 71 | return this.Content; 72 | } 73 | 74 | private void Shift(int index, int offset) 75 | { 76 | for (int i = this.Blocks.Count - 1; i >= 0; i--) 77 | { 78 | Block block = this.Blocks[i]; 79 | 80 | if (block.Begin > index) 81 | { 82 | this.ShiftAfterBlock(block, offset); 83 | } 84 | else 85 | { 86 | if (block.End >= index) 87 | { 88 | this.ShiftInsideBlock(index, block, offset); 89 | } 90 | else 91 | { 92 | break; 93 | } 94 | } 95 | } 96 | 97 | for (int i = this.Styles.Count - 1; i >= 0; i--) 98 | { 99 | Style style = this.Styles[i]; 100 | 101 | if (!this.ShiftStyle(index, style, offset)) 102 | { 103 | break; 104 | } 105 | } 106 | } 107 | 108 | private void ShiftInsideBlock(int index, Block block, int offset) 109 | { 110 | block.End += offset; 111 | 112 | if (block.NameEnd >= index) 113 | { 114 | block.NameEnd += offset; 115 | } 116 | 117 | for (int i = block.Styles.Count - 1; i >= 0; i--) 118 | { 119 | Style style = block.Styles[i]; 120 | 121 | if (!this.ShiftStyle(index, style, offset)) 122 | { 123 | break; 124 | } 125 | } 126 | } 127 | 128 | private bool ShiftStyle(int index, Style style, int offset) 129 | { 130 | if (style.Begin >= index) 131 | { 132 | this.ShiftAfterStyle(style, offset); 133 | 134 | return true; 135 | } 136 | else 137 | { 138 | if (style.End >= index) 139 | { 140 | this.ShiftInsideStyle(index, style, offset); 141 | 142 | return true; 143 | } 144 | else 145 | { 146 | return false; 147 | } 148 | } 149 | } 150 | 151 | private void ShiftInsideStyle(int index, Style style, int offset) 152 | { 153 | style.End += offset; 154 | 155 | if (style.SelectorEnd >= index) 156 | { 157 | style.SelectorEnd += offset; 158 | } 159 | 160 | for (int i = style.Properties.Count - 1; i >= 0; i--) 161 | { 162 | Property property = style.Properties[i]; 163 | 164 | if (property.Begin > index) 165 | { 166 | property.ShiftAfterProperty(offset); 167 | } 168 | else 169 | { 170 | if (property.End >= index) 171 | { 172 | property.ShiftInsideProperty(index, offset); 173 | } 174 | else 175 | { 176 | break; 177 | } 178 | } 179 | } 180 | } 181 | 182 | private void ShiftAfterBlock(Block block, int offset) 183 | { 184 | block.Begin += offset; 185 | block.End += offset; 186 | block.NameEnd += offset; 187 | 188 | foreach (Style i in block.Styles) 189 | { 190 | this.ShiftAfterStyle(i, offset); 191 | } 192 | } 193 | 194 | private void ShiftAfterStyle(Style style, int offset) 195 | { 196 | style.Begin += offset; 197 | style.End += offset; 198 | style.SelectorEnd += offset; 199 | 200 | foreach (Property i in style.Properties) 201 | { 202 | i.ShiftAfterProperty(offset); 203 | } 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Less.Html/Styles/CssInfo.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace Less.Html 8 | { 9 | /// 10 | /// css 信息 11 | /// 12 | public abstract class CssInfo 13 | { 14 | /// 15 | /// 获取以空白字符分开的属性值的正则表达式 16 | /// 17 | protected static Regex SpaceValuePattern 18 | { 19 | get; 20 | set; 21 | } 22 | 23 | /// 24 | /// 获取以逗号分开的属性值的正则表达式 25 | /// 26 | protected static Regex CommaValuePattern 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | /// 33 | /// 获取链接值的正则表达式 34 | /// 35 | protected static Regex UrlPattern 36 | { 37 | get; 38 | set; 39 | } 40 | 41 | /// 42 | /// 所属文档 43 | /// 44 | internal Css OwnerCss 45 | { 46 | get; 47 | set; 48 | } 49 | 50 | internal int Begin 51 | { 52 | get; 53 | set; 54 | } 55 | 56 | internal int End 57 | { 58 | get; 59 | set; 60 | } 61 | 62 | static CssInfo() 63 | { 64 | CssInfo.SpaceValuePattern = @"\S+".ToRegex( 65 | RegexOptions.IgnorePatternWhitespace | 66 | RegexOptions.Compiled); 67 | 68 | CssInfo.CommaValuePattern = "[^,]+".ToRegex( 69 | RegexOptions.IgnorePatternWhitespace | 70 | RegexOptions.Compiled); 71 | 72 | CssInfo.UrlPattern = @" 73 | url\((?[""'])(?.*?)\k\)| 74 | url\((?.*?)\) 75 | ".ToRegex( 76 | RegexOptions.IgnorePatternWhitespace | 77 | RegexOptions.IgnoreCase | 78 | RegexOptions.Compiled | 79 | RegexOptions.ExplicitCapture); 80 | } 81 | 82 | /// 83 | /// 初始化 84 | /// 85 | /// 86 | protected CssInfo(Css ownerCss) : this(ownerCss, -1) 87 | { 88 | // 89 | } 90 | 91 | /// 92 | /// 初始化 93 | /// 94 | /// 95 | /// 96 | protected CssInfo(Css ownerCss, int begin) : this(ownerCss, begin, -1) 97 | { 98 | // 99 | } 100 | 101 | /// 102 | /// 初始化 103 | /// 104 | /// 105 | /// 106 | /// 107 | protected CssInfo(Css ownerCss, int begin, int end) 108 | { 109 | this.OwnerCss = ownerCss; 110 | this.Begin = begin; 111 | this.End = end; 112 | } 113 | 114 | /// 115 | /// 字面量 116 | /// 117 | /// 118 | public override string ToString() 119 | { 120 | return this.OwnerCss.Content.SubstringUnsafe(this.Begin, this.End - this.Begin + 1); 121 | } 122 | 123 | /// 124 | /// 枚举以逗号分隔的内容 125 | /// 126 | /// 127 | /// 128 | protected void EachCommaValue(string content, Action action) 129 | { 130 | int position = 0; 131 | 132 | Match match = CssInfo.CommaValuePattern.Match(content, position); 133 | 134 | while (match.Success) 135 | { 136 | action(match); 137 | 138 | position += match.Length; 139 | 140 | match = CssInfo.CommaValuePattern.Match(content, position); 141 | } 142 | } 143 | 144 | /// 145 | /// 枚举以逗号分隔的内容 146 | /// 147 | /// 148 | /// 149 | protected void EachSpaceValue(string content, Action action) 150 | { 151 | int position = 0; 152 | 153 | Match match = CssInfo.SpaceValuePattern.Match(content, position); 154 | 155 | while (match.Success) 156 | { 157 | action(match); 158 | 159 | position += match.Length; 160 | 161 | match = CssInfo.SpaceValuePattern.Match(content, position); 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Less.Html/Styles/Properties/Background.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 背景 10 | /// 11 | public class Background : Property 12 | { 13 | private int UrlBegin 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | private int UrlEnd 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | private bool HasUrl 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | /// 32 | /// url 33 | /// 34 | public string Url 35 | { 36 | get 37 | { 38 | if (HasUrl) 39 | { 40 | return this.OwnerCss.Content.SubstringUnsafe(this.UrlBegin, this.UrlEnd - this.UrlBegin + 1); 41 | } 42 | else 43 | { 44 | return null; 45 | } 46 | } 47 | set 48 | { 49 | if (HasUrl) 50 | { 51 | this.OwnerCss.Replace(this.UrlBegin, this.UrlEnd - this.UrlBegin + 1, value); 52 | } 53 | else 54 | { 55 | // 56 | } 57 | } 58 | } 59 | 60 | internal Background(Property property) : base(property) 61 | { 62 | this.EachSpaceValue(property.Value, match => 63 | { 64 | Match matchUrl = CssInfo.UrlPattern.Match(match.Value); 65 | 66 | if (matchUrl.Success) 67 | { 68 | Group url = matchUrl.Groups["url"]; 69 | 70 | this.UrlBegin = property.ValueBegin + match.Index + url.Index; 71 | 72 | this.UrlEnd = this.UrlBegin + url.Length - 1; 73 | 74 | this.HasUrl = true; 75 | } 76 | }); 77 | } 78 | 79 | /// 80 | /// 偏移发生了内容变动的属性值的索引 81 | /// 82 | /// 83 | /// 84 | protected override void ShiftInsideValue(int index, int offset) 85 | { 86 | if (HasUrl) 87 | { 88 | if (this.UrlBegin > index) 89 | { 90 | this.ShiftAfterValue(offset); 91 | } 92 | else 93 | { 94 | if (this.UrlEnd >= index) 95 | { 96 | this.UrlEnd += offset; 97 | } 98 | } 99 | } 100 | } 101 | 102 | /// 103 | /// 偏移文档变动位置后面的属性值的索引 104 | /// 105 | /// 106 | protected override void ShiftAfterValue(int offset) 107 | { 108 | if (HasUrl) 109 | { 110 | this.UrlBegin += offset; 111 | this.UrlEnd += offset; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Less.Html/Styles/Properties/BackgroundImage.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 背景图片 10 | /// 11 | public class BackgroundImage : Property 12 | { 13 | private int UrlBegin 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | private int UrlEnd 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | private bool HasUrl 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | /// 32 | /// url 33 | /// 34 | public string Url 35 | { 36 | get 37 | { 38 | if (HasUrl) 39 | { 40 | return this.OwnerCss.Content.SubstringUnsafe(this.UrlBegin, this.UrlEnd - this.UrlBegin + 1); 41 | } 42 | else 43 | { 44 | return null; 45 | } 46 | } 47 | set 48 | { 49 | if (HasUrl) 50 | { 51 | this.OwnerCss.Replace(this.UrlBegin, this.UrlEnd - this.UrlBegin + 1, value); 52 | } 53 | else 54 | { 55 | // 56 | } 57 | } 58 | } 59 | 60 | internal BackgroundImage(Property property) : base(property) 61 | { 62 | this.EachSpaceValue(property.Value, match => 63 | { 64 | Match matchUrl = CssInfo.UrlPattern.Match(match.Value); 65 | 66 | if (matchUrl.Success) 67 | { 68 | Group url = matchUrl.Groups["url"]; 69 | 70 | this.UrlBegin = property.ValueBegin + match.Index + url.Index; 71 | 72 | this.UrlEnd = this.UrlBegin + url.Length - 1; 73 | 74 | this.HasUrl = true; 75 | } 76 | }); 77 | } 78 | 79 | /// 80 | /// 偏移发生了内容变动的属性值的索引 81 | /// 82 | /// 83 | /// 84 | protected override void ShiftInsideValue(int index, int offset) 85 | { 86 | if (HasUrl) 87 | { 88 | if (this.UrlBegin > index) 89 | { 90 | this.ShiftAfterValue(offset); 91 | } 92 | else 93 | { 94 | if (this.UrlEnd >= index) 95 | { 96 | this.UrlEnd += offset; 97 | } 98 | } 99 | } 100 | } 101 | 102 | /// 103 | /// 偏移文档变动位置后面的属性值的索引 104 | /// 105 | /// 106 | protected override void ShiftAfterValue(int offset) 107 | { 108 | if (HasUrl) 109 | { 110 | this.UrlBegin += offset; 111 | this.UrlEnd += offset; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Less.Html/Styles/Properties/Src/Src.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Less.Html 6 | { 7 | /// 8 | /// 源文件 9 | /// 10 | public class Src : Property 11 | { 12 | /// 13 | /// 值集合 14 | /// 15 | public SrcValueCollection Values 16 | { 17 | get; 18 | private set; 19 | } 20 | 21 | internal Src(Property property) : base(property) 22 | { 23 | this.Values = new SrcValueCollection(); 24 | 25 | this.EachCommaValue(property.Value, match => 26 | { 27 | int begin = this.ValueBegin + match.Index; 28 | 29 | int end = begin + match.Length - 1; 30 | 31 | this.Values.Add(new SrcValue(this.OwnerCss, begin, end)); 32 | }); 33 | } 34 | 35 | /// 36 | /// 偏移发生了内容变动的属性值的索引 37 | /// 38 | /// 39 | /// 40 | protected override void ShiftInsideValue(int index, int offset) 41 | { 42 | for (int i = this.Values.Count - 1; i >= 0; i--) 43 | { 44 | SrcValue value = this.Values[i]; 45 | 46 | if (value.HasUrl) 47 | { 48 | if (value.UrlEnd >= index) 49 | { 50 | value.UrlEnd += offset; 51 | 52 | if (value.UrlBegin > index) 53 | { 54 | value.UrlBegin += offset; 55 | } 56 | } 57 | else 58 | { 59 | break; 60 | } 61 | } 62 | } 63 | } 64 | 65 | /// 66 | /// 偏移文档变动位置后面的属性值的索引 67 | /// 68 | /// 69 | protected override void ShiftAfterValue(int offset) 70 | { 71 | foreach (SrcValue i in this.Values) 72 | { 73 | if (i.HasUrl) 74 | { 75 | i.UrlBegin += offset; 76 | i.UrlEnd += offset; 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Less.Html/Styles/Properties/Src/SrcValue.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 源文件值 10 | /// 11 | public class SrcValue : CssInfo 12 | { 13 | internal int UrlBegin 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | internal int UrlEnd 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | internal bool HasUrl 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | /// 32 | /// url 33 | /// 34 | public string Url 35 | { 36 | get 37 | { 38 | if (HasUrl) 39 | { 40 | return this.OwnerCss.Content.SubstringUnsafe(this.UrlBegin, this.UrlEnd - this.UrlBegin + 1); 41 | } 42 | else 43 | { 44 | return null; 45 | } 46 | } 47 | set 48 | { 49 | if (HasUrl) 50 | { 51 | this.OwnerCss.Replace(this.UrlBegin, this.UrlEnd - this.UrlBegin + 1, value); 52 | } 53 | else 54 | { 55 | // 56 | } 57 | } 58 | } 59 | 60 | internal SrcValue(Css ownerCss, int begin, int end) : base(ownerCss, begin, end) 61 | { 62 | this.EachSpaceValue(this.ToString(), match => 63 | { 64 | Match matchUrl = Property.UrlPattern.Match(match.Value); 65 | 66 | if (matchUrl.Success) 67 | { 68 | Group url = matchUrl.Groups["url"]; 69 | 70 | this.UrlBegin = this.Begin + match.Index + url.Index; 71 | 72 | this.UrlEnd = this.UrlBegin + url.Length - 1; 73 | 74 | this.HasUrl = true; 75 | } 76 | }); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Less.Html/Styles/Properties/Src/SrcValueCollection.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 源文件值集合 10 | /// 11 | public class SrcValueCollection : IEnumerable 12 | { 13 | private List List 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | /// 20 | /// 集合计数 21 | /// 22 | public int Count 23 | { 24 | get 25 | { 26 | return this.List.Count; 27 | } 28 | } 29 | 30 | /// 31 | /// 获取指定索引的源文件值 32 | /// 33 | /// 34 | /// 35 | public SrcValue this[int index] 36 | { 37 | get 38 | { 39 | return this.List[index]; 40 | } 41 | } 42 | 43 | internal SrcValueCollection() 44 | { 45 | this.List = new List(); 46 | } 47 | 48 | internal void Add(SrcValue value) 49 | { 50 | this.List.Add(value); 51 | } 52 | 53 | /// 54 | /// 获取迭代器 55 | /// 56 | /// 57 | public IEnumerator GetEnumerator() 58 | { 59 | return this.List.GetEnumerator(); 60 | } 61 | 62 | IEnumerator IEnumerable.GetEnumerator() 63 | { 64 | return this.List.GetEnumerator(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Less.Html/Styles/Property.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Less.Html 7 | { 8 | /// 9 | /// 属性 10 | /// 11 | public class Property : CssInfo 12 | { 13 | internal int NameEnd 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | internal int ValueBegin 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | internal int ValueEnd 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | /// 32 | /// 属性名 33 | /// 34 | public string Name 35 | { 36 | get 37 | { 38 | return this.OwnerCss.Content.SubstringUnsafe(this.Begin, this.NameEnd - this.Begin + 1); 39 | } 40 | } 41 | 42 | /// 43 | /// 属性值 44 | /// 45 | public string Value 46 | { 47 | get 48 | { 49 | return this.OwnerCss.Content.SubstringUnsafe(this.ValueBegin, this.ValueEnd - this.ValueBegin + 1); 50 | } 51 | } 52 | 53 | /// 54 | /// 初始化 55 | /// 56 | /// 57 | protected Property(Property property) : this( 58 | property.OwnerCss, property.Begin, property.NameEnd, property.ValueBegin, property.ValueEnd, property.End) 59 | { 60 | // 61 | } 62 | 63 | internal Property(Css ownerCss, int begin, int nameEnd, int valueBegin, int valueEnd, int end) : base(ownerCss, begin, end) 64 | { 65 | this.NameEnd = nameEnd; 66 | this.ValueBegin = valueBegin; 67 | this.ValueEnd = valueEnd; 68 | } 69 | 70 | /// 71 | /// 偏移发生了内容变动的属性的索引 72 | /// 73 | /// 74 | /// 75 | internal void ShiftInsideProperty(int index, int offset) 76 | { 77 | this.End += offset; 78 | 79 | if (this.ValueEnd >= index) 80 | { 81 | this.ValueEnd += offset; 82 | 83 | if (this.ValueBegin > index) 84 | { 85 | this.ValueBegin += offset; 86 | } 87 | 88 | if (this.NameEnd >= index) 89 | { 90 | this.NameEnd += offset; 91 | } 92 | } 93 | 94 | this.ShiftInsideValue(index, offset); 95 | } 96 | 97 | /// 98 | /// 偏移发生了内容变动的属性值的索引 99 | /// 100 | /// 101 | /// 102 | protected virtual void ShiftInsideValue(int index, int offset) 103 | { 104 | // 105 | } 106 | 107 | /// 108 | /// 偏移文档变动位置后面的属性的索引 109 | /// 110 | /// 111 | internal void ShiftAfterProperty(int offset) 112 | { 113 | this.Begin += offset; 114 | this.NameEnd += offset; 115 | this.ValueBegin += offset; 116 | this.ValueEnd += offset; 117 | this.End += offset; 118 | 119 | this.ShiftAfterValue(offset); 120 | } 121 | 122 | /// 123 | /// 偏移文档变动位置后面的属性值的索引 124 | /// 125 | /// 126 | protected virtual void ShiftAfterValue(int offset) 127 | { 128 | // 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Less.Html/Styles/PropertyCollection.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Less.Html 9 | { 10 | /// 11 | /// 属性集合 12 | /// 13 | public class PropertyCollection : IEnumerable 14 | { 15 | private List List 16 | { 17 | get; 18 | set; 19 | } 20 | 21 | /// 22 | /// 属性计数 23 | /// 24 | public int Count 25 | { 26 | get 27 | { 28 | return this.List.Count; 29 | } 30 | } 31 | 32 | /// 33 | /// 获取指定索引的属性 34 | /// 35 | /// 36 | /// 37 | public Property this[int index] 38 | { 39 | get 40 | { 41 | return this.List[index]; 42 | } 43 | } 44 | 45 | /// 46 | /// 获取指定名称的属性 47 | /// 48 | /// 49 | /// 50 | public Property[] this[string name] 51 | { 52 | get 53 | { 54 | return this.List.Where(i => i.Name.CompareIgnoreCase(name)).ToArray(); 55 | } 56 | } 57 | 58 | internal PropertyCollection() 59 | { 60 | this.List = new List(); 61 | } 62 | 63 | internal void Add(Property property) 64 | { 65 | this.List.Add(property); 66 | } 67 | 68 | /// 69 | /// 获取迭代器 70 | /// 71 | /// 72 | public IEnumerator GetEnumerator() 73 | { 74 | return this.List.GetEnumerator(); 75 | } 76 | 77 | IEnumerator IEnumerable.GetEnumerator() 78 | { 79 | return this.List.GetEnumerator(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Less.Html/Styles/Style.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using Less.Text; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Less.Html 8 | { 9 | /// 10 | /// 样式 11 | /// 12 | public class Style : CssInfo 13 | { 14 | private Dictionary> Handlers 15 | { 16 | get; 17 | set; 18 | } 19 | 20 | internal int SelectorEnd 21 | { 22 | get; 23 | set; 24 | } 25 | 26 | /// 27 | /// 选择器 28 | /// 29 | public string Selector 30 | { 31 | get 32 | { 33 | return this.OwnerCss.Content.SubstringUnsafe(this.Begin, this.SelectorEnd - this.Begin + 1); 34 | } 35 | } 36 | 37 | /// 38 | /// 属性集合 39 | /// 40 | public PropertyCollection Properties 41 | { 42 | get; 43 | private set; 44 | } 45 | 46 | /// 47 | /// 背景 48 | /// 49 | public Background Background 50 | { 51 | get; 52 | private set; 53 | } 54 | 55 | /// 56 | /// 背景图片 57 | /// 58 | public BackgroundImage BackgroundImage 59 | { 60 | get; 61 | private set; 62 | } 63 | 64 | /// 65 | /// 源文件 66 | /// 67 | public Src Src 68 | { 69 | get; 70 | private set; 71 | } 72 | 73 | internal Style(Css ownerCss, int begin, int selectorEnd) : this(ownerCss, begin) 74 | { 75 | this.SelectorEnd = selectorEnd; 76 | } 77 | 78 | internal Style(Css ownerCss, int begin) : base(ownerCss, begin) 79 | { 80 | this.Handlers = new Dictionary>(StringComparer.OrdinalIgnoreCase); 81 | 82 | this.Handlers.Add("background", property => this.Background = new Background(property)); 83 | this.Handlers.Add("background-image", property => this.BackgroundImage = new BackgroundImage(property)); 84 | this.Handlers.Add("src", property => this.Src = new Src(property)); 85 | 86 | this.Properties = new PropertyCollection(); 87 | } 88 | 89 | internal void Add(Property property) 90 | { 91 | Func func; 92 | 93 | if (this.Handlers.TryGetValue(property.Name, out func)) 94 | { 95 | this.Properties.Add(func(property)); 96 | } 97 | else 98 | { 99 | this.Properties.Add(property); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Less.Html/Styles/StyleCollection.cs: -------------------------------------------------------------------------------- 1 | //bibaoke.com 2 | 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Less.Text; 7 | 8 | namespace Less.Html 9 | { 10 | /// 11 | /// 样式集合 12 | /// 13 | public class StyleCollection : IEnumerable