├── .gitignore
├── LICENSE.txt
├── README.md
└── VaderSharp
├── NuGet-Pack.bat
├── VaderSharp.sln
├── VaderSharp
├── ConfigStore
│ ├── ConfigStore.cs
│ └── strings
│ │ └── en-gb.xml
├── Extensions.cs
├── SentiText.cs
├── SentimentAnalysisResults.cs
├── SentimentIntensityAnalyzer.cs
├── SentimentUtils.cs
├── VaderSharp.csproj
└── vader_lexicon.txt
└── VaderSharpTestCore
├── SentimentTest.cs
└── VaderSharpTestCore.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 | *.appx
187 |
188 | # Visual Studio cache files
189 | # files ending in .cache can be ignored
190 | *.[Cc]ache
191 | # but keep track of directories ending in .cache
192 | !*.[Cc]ache/
193 |
194 | # Others
195 | ClientBin/
196 | ~$*
197 | *~
198 | *.dbmdl
199 | *.dbproj.schemaview
200 | *.jfm
201 | *.pfx
202 | *.publishsettings
203 | orleans.codegen.cs
204 |
205 | # Since there are multiple workflows, uncomment next line to ignore bower_components
206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
207 | #bower_components/
208 |
209 | # RIA/Silverlight projects
210 | Generated_Code/
211 |
212 | # Backup & report files from converting an old project file
213 | # to a newer Visual Studio version. Backup files are not needed,
214 | # because we have git ;-)
215 | _UpgradeReport_Files/
216 | Backup*/
217 | UpgradeLog*.XML
218 | UpgradeLog*.htm
219 |
220 | # SQL Server files
221 | *.mdf
222 | *.ldf
223 | *.ndf
224 |
225 | # Business Intelligence projects
226 | *.rdl.data
227 | *.bim.layout
228 | *.bim_*.settings
229 |
230 | # Microsoft Fakes
231 | FakesAssemblies/
232 |
233 | # GhostDoc plugin setting file
234 | *.GhostDoc.xml
235 |
236 | # Node.js Tools for Visual Studio
237 | .ntvs_analysis.dat
238 | node_modules/
239 |
240 | # Typescript v1 declaration files
241 | typings/
242 |
243 | # Visual Studio 6 build log
244 | *.plg
245 |
246 | # Visual Studio 6 workspace options file
247 | *.opt
248 |
249 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
250 | *.vbw
251 |
252 | # Visual Studio LightSwitch build output
253 | **/*.HTMLClient/GeneratedArtifacts
254 | **/*.DesktopClient/GeneratedArtifacts
255 | **/*.DesktopClient/ModelManifest.xml
256 | **/*.Server/GeneratedArtifacts
257 | **/*.Server/ModelManifest.xml
258 | _Pvt_Extensions
259 |
260 | # Paket dependency manager
261 | .paket/paket.exe
262 | paket-files/
263 |
264 | # FAKE - F# Make
265 | .fake/
266 |
267 | # JetBrains Rider
268 | .idea/
269 | *.sln.iml
270 |
271 | # CodeRush
272 | .cr/
273 |
274 | # Python Tools for Visual Studio (PTVS)
275 | __pycache__/
276 | *.pyc
277 |
278 | # Cake - Uncomment if you are using it
279 | # tools/**
280 | # !tools/packages.config
281 |
282 | # Telerik's JustMock configuration file
283 | *.jmconfig
284 |
285 | # BizTalk build output
286 | *.btp.cs
287 | *.btm.cs
288 | *.odx.cs
289 | *.xsd.cs
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Jordan Andrews
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VaderSharp. The best sentiment analysis tool. In C#.
2 |
3 | "VADER (Valence Aware Dictionary and sEntiment Reasoner) is a lexicon and rule-based sentiment analysis tool that is specifically attuned to sentiments expressed in social media."
4 |
5 | Previously VADER was only available in python (https://github.com/cjhutto/vaderSentiment). I wanted to use it in C# so ported it over.
6 |
7 | # Getting Started
8 |
9 | VaderSharp supports:
10 |
11 | - .NET Core
12 | - .NET Framework 3.5 and above
13 | - Mono & Xamarin
14 | - UWP
15 |
16 | To install VaderSharp, run the following command in the Package Manager Console:
17 |
18 | ```
19 | Install-Package CodingUpAStorm.VaderSharp
20 | ```
21 |
22 | # Usage
23 |
24 | Import the package at the top of the page:
25 |
26 | ```c#
27 | using VaderSharp;
28 | ```
29 |
30 | Then just initialize an instance of SentimentIntensityAnalyzer and call it's PolarityScores method:
31 |
32 | ```c#
33 | SentimentIntensityAnalyzer analyzer = new SentimentIntensityAnalyzer();
34 |
35 | var results = analyzer.PolarityScores("Wow, this package is amazingly easy to use");
36 |
37 | Console.WriteLine("Positive score: " + results.Positive);
38 | Console.WriteLine("Negative score: " + results.Negative);
39 | Console.WriteLine("Neutral score: " + results.Neutral);
40 | Console.WriteLine("Compound score: " + results.Compound);
41 | ```
42 |
--------------------------------------------------------------------------------
/VaderSharp/NuGet-Pack.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | set version=%1
4 |
5 | dotnet restore .\VaderSharp
6 | dotnet pack .\VaderSharp\VaderSharp.csproj --output ..\nupkgs --configuration Release /p:PackageVersion=%version% --include-symbols --include-source
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26403.7
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VaderSharp", "VaderSharp\VaderSharp.csproj", "{8216CD49-1D2E-4F93-A69E-6BF4006247B6}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VaderSharpTestCore", "VaderSharpTestCore\VaderSharpTestCore.csproj", "{4F46C368-5EEE-4D24-80FE-BDB555F7C01E}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {8216CD49-1D2E-4F93-A69E-6BF4006247B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {8216CD49-1D2E-4F93-A69E-6BF4006247B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {8216CD49-1D2E-4F93-A69E-6BF4006247B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {8216CD49-1D2E-4F93-A69E-6BF4006247B6}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {4F46C368-5EEE-4D24-80FE-BDB555F7C01E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {4F46C368-5EEE-4D24-80FE-BDB555F7C01E}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {4F46C368-5EEE-4D24-80FE-BDB555F7C01E}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {4F46C368-5EEE-4D24-80FE-BDB555F7C01E}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/ConfigStore/ConfigStore.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Xml.Linq;
5 |
6 | namespace VaderSharp
7 | {
8 |
9 | ///
10 | /// Proof of concept for loading the words to be used as boosters, negations etc.
11 | ///
12 | /// Currently not used.
13 | ///
14 | public class ConfigStore
15 | {
16 |
17 | private static ConfigStore config;
18 |
19 | public Dictionary BoosterDict { get; private set; }
20 |
21 | public string[] Negations { get; private set; }
22 |
23 | public Dictionary SpecialCaseIdioms { get; private set; }
24 |
25 | private ConfigStore(string languageCode)
26 | {
27 | LoadConfig(languageCode);
28 | }
29 |
30 | ///
31 | ///
32 | ///
33 | /// Language code in writing style "language-country". Default is British English.
34 | /// ConfigStore object.
35 | public static ConfigStore CreateConfig(string languageCode = "en-gb")
36 | {
37 | config = config ?? new ConfigStore(languageCode);
38 | return config;
39 | }
40 |
41 | ///
42 | /// Initializes the ConfigStore and loads the config file.
43 | ///
44 | /// Language code in writing style "language-country".
45 | private void LoadConfig(string languageCode)
46 | {
47 | string path = $"D:/Daten/Repositories/vadersharp/VaderSharp/VaderSharp/strings/{languageCode}.xml";
48 | if (!File.Exists(path))
49 | {
50 | throw new FileNotFoundException("Language file was not found. Please check language code.");
51 | }
52 | XElement root = XDocument.Load(path).Document.Root;
53 | LoadNegations(root);
54 | LoadIdioms(root);
55 | LoadBooster(root);
56 | }
57 |
58 | ///
59 | /// Loads negations from config file.
60 | ///
61 | /// Root element of XML document
62 | private void LoadNegations(XElement root)
63 | {
64 | var nodes = root.Descendants(XName.Get("negation"));
65 | int length = nodes.Count();
66 | Negations = new string[length];
67 | for (int i = 0; i < length; i++)
68 | {
69 | Negations[i] = nodes.ElementAt(i).Value;
70 | }
71 | }
72 |
73 | ///
74 | /// Loads idioms from config file.
75 | ///
76 | /// Root element of XML document
77 | private void LoadIdioms(XElement root)
78 | {
79 | SpecialCaseIdioms = new Dictionary();
80 | var nodes = root.Descendants(XName.Get("idiom"));
81 | double value;
82 | foreach (var n in nodes)
83 | {
84 | value = double.Parse(n.Attribute(XName.Get("value")).Value);
85 | SpecialCaseIdioms.Add(n.Value, value);
86 | }
87 | }
88 |
89 | ///
90 | /// Loads booster words from config file.
91 | ///
92 | /// Root element of XML document
93 | private void LoadBooster(XElement root)
94 | {
95 | BoosterDict = new Dictionary();
96 | var nodes = root.Descendants(XName.Get("booster"));
97 | double sign;
98 | foreach (var n in nodes)
99 | {
100 | sign = n.Attribute(XName.Get("sign")).Value == "BIncr" ? 0.293 : -0.293;
101 | BoosterDict.Add(n.Value, sign);
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/ConfigStore/strings/en-gb.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | absolutely
5 | amazingly
6 | awfully
7 | completely
8 | considerably
9 | decidedly
10 | deeply
11 | effing
12 | enormously
13 | entirely
14 | especially
15 | exceptionally
16 | extremely
17 | fabulously
18 | flipping
19 | flippin
20 | fricking
21 | frickin
22 | frigging
23 | friggin
24 | fully
25 | fucking
26 | greatly
27 | hella
28 | highly
29 | hugely
30 | incredibly
31 | intensely
32 | majorly
33 | more
34 | most
35 | particularly
36 | purely
37 | quite
38 | really
39 | remarkably
40 | so
41 | substantially
42 | thoroughly
43 | totally
44 | tremendously
45 | uber
46 | unbelievably
47 | unusually
48 | utterly
49 | very
50 | almost
51 | barely
52 | hardly
53 | just enough
54 | kind of
55 | kinda
56 | kindof
57 | kind-of
58 | less
59 | little
60 | marginally
61 | occasionally
62 | partly
63 | scarcely
64 | slightly
65 | somewhat
66 | sort of
67 | sorta
68 | sortof
69 | sort-of
70 |
71 |
72 | aint
73 | arent
74 | cannot
75 | cant
76 | couldnt
77 | darent
78 | didnt
79 | doesnt
80 | ain't
81 | aren't
82 | can't
83 | couldn't
84 | daren't
85 | didn't
86 | doesn't
87 | dont
88 | hadnt
89 | hasnt
90 | havent
91 | isnt
92 | mightnt
93 | mustnt
94 | neither
95 | don't
96 | hadn't
97 | hasn't
98 | haven't
99 | isn't
100 | mightn't
101 | mustn't
102 | neednt
103 | needn't
104 | never
105 | none
106 | nope
107 | nor
108 | not
109 | nothing
110 | nowhere
111 | oughtnt
112 | shant
113 | shouldnt
114 | uhuh
115 | wasnt
116 | werent
117 | oughtn't
118 | shan't
119 | shouldn't
120 | uh-uh
121 | wasn't
122 | weren't
123 | without
124 | wont
125 | wouldnt
126 | won't
127 | wouldn't
128 | rarely
129 | seldom
130 | despite
131 |
132 |
133 | the shit
134 | the bomb
135 | bad ass
136 | yeah right
137 | cut the mustard
138 | kiss of death
139 | hand to mouth
140 |
141 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace VaderSharp
4 | {
5 | internal static class Extensions
6 | {
7 | ///
8 | /// Determine if word is ALL CAPS
9 | ///
10 | ///
11 | ///
12 | public static bool IsUpper(this string word)
13 | {
14 | return !word.Any(char.IsLower);
15 | }
16 |
17 | ///
18 | /// Removes punctuation from word
19 | ///
20 | ///
21 | ///
22 | public static string RemovePunctuation(this string word)
23 | {
24 | return new string(word.Where(c => !char.IsPunctuation(c)).ToArray());
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/SentiText.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace VaderSharp
5 | {
6 | internal class SentiText
7 | {
8 | private string Text { get; }
9 | public IList WordsAndEmoticons { get; }
10 | public bool IsCapDifferential { get; }
11 |
12 | public SentiText(string text)
13 | {
14 | //TODO: Encode in UTF-8 ?
15 | Text = text;
16 | WordsAndEmoticons = GetWordsAndEmoticons();
17 | IsCapDifferential = SentimentUtils.AllCapDifferential(WordsAndEmoticons);
18 | }
19 |
20 |
21 | ///
22 | /// Returns mapping of the form {'cat,': 'cat'}, {',cat': 'cat'}
23 | ///
24 | ///
25 | private Dictionary WordsPlusPunc()
26 | {
27 | string noPuncText = Text.RemovePunctuation();
28 | var wordsOnly = noPuncText.Split().Where(x=>x.Length > 1);
29 |
30 | //for each word in wordsOnly, get each possible variant of punclist before/after
31 | //Seems poor. Maybe I can improve in future.
32 | Dictionary puncDic = new Dictionary();
33 | foreach (var word in wordsOnly)
34 | {
35 | foreach (var punc in SentimentUtils.PuncList)
36 | {
37 | if (puncDic.ContainsKey(word + punc))
38 | continue;
39 |
40 | puncDic.Add(word + punc, word);
41 | puncDic.Add(punc + word, word);
42 | }
43 | }
44 | return puncDic;
45 | }
46 |
47 |
48 | ///
49 | /// Removes leading and trailing punctuation. Leaves contractions and most emoticons.
50 | ///
51 | ///
52 | private IList GetWordsAndEmoticons()
53 | {
54 | IList wes = Text.Split().Where(x=> x.Length > 1).ToList();
55 | Dictionary wordsPuncDic = WordsPlusPunc();
56 | for (int i = 0; i < wes.Count; i++)
57 | {
58 | if (wordsPuncDic.ContainsKey(wes[i]))
59 | wes[i] = wordsPuncDic[wes[i]];
60 | }
61 |
62 | return wes;
63 | }
64 |
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/SentimentAnalysisResults.cs:
--------------------------------------------------------------------------------
1 | namespace VaderSharp
2 | {
3 | ///
4 | /// A model to represent the result of analysis.
5 | ///
6 | public class SentimentAnalysisResults
7 | {
8 | ///
9 | /// The proportion of words in the sentence with negative valence.
10 | ///
11 | public double Negative { get; set; }
12 |
13 | ///
14 | /// The proportion of words in the sentence with no valence.
15 | ///
16 | public double Neutral { get; set; }
17 |
18 | ///
19 | /// The proportion of words in the sentence with positive valence.
20 | ///
21 | public double Positive { get; set; }
22 |
23 | ///
24 | /// Normalized sentiment score between -1 and 1.
25 | ///
26 | public double Compound { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/SentimentIntensityAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace VaderSharp
6 | {
7 | using System.IO;
8 | using System.Reflection;
9 |
10 | ///
11 | /// An abstraction to represent the sentiment intensity analyzer.
12 | ///
13 | public class SentimentIntensityAnalyzer
14 | {
15 | private const double ExclIncr = 0.292;
16 | private const double QuesIncrSmall = 0.18;
17 | private const double QuesIncrLarge = 0.96;
18 |
19 | private static Dictionary Lexicon { get; }
20 | private static string[] LexiconFullFile { get; }
21 |
22 | static SentimentIntensityAnalyzer()
23 | {
24 | Assembly assembly;
25 | #if NET_35
26 | assembly = typeof(SentimentIntensityAnalyzer).Assembly;
27 | #else
28 | assembly = typeof(SentimentIntensityAnalyzer).GetTypeInfo().Assembly;
29 | #endif
30 | using (var stream = assembly.GetManifestResourceStream("VaderSharp.vader_lexicon.txt"))
31 | using(var reader = new StreamReader(stream))
32 | {
33 | LexiconFullFile = reader.ReadToEnd().Split('\n');
34 | Lexicon = MakeLexDic();
35 | }
36 | }
37 |
38 | private static Dictionary MakeLexDic()
39 | {
40 | var dic = new Dictionary();
41 | foreach (var line in LexiconFullFile)
42 | {
43 | var lineArray = line.Trim().Split('\t');
44 | dic.Add(lineArray[0], Double.Parse(lineArray[1]));
45 | }
46 | return dic;
47 | }
48 |
49 | ///
50 | /// Return metrics for positive, negative and neutral sentiment based on the input text.
51 | ///
52 | ///
53 | ///
54 | public SentimentAnalysisResults PolarityScores(string input)
55 | {
56 | SentiText sentiText = new SentiText(input);
57 | IList sentiments = new List();
58 | IList wordsAndEmoticons = sentiText.WordsAndEmoticons;
59 |
60 | for (int i = 0; i < wordsAndEmoticons.Count; i++)
61 | {
62 | string item = wordsAndEmoticons[i];
63 | double valence = 0;
64 | if (i < wordsAndEmoticons.Count - 1 && item.ToLower() == "kind" && wordsAndEmoticons[i + 1] == "of"
65 | || SentimentUtils.BoosterDict.ContainsKey(item.ToLower()))
66 | {
67 | sentiments.Add(valence);
68 | continue;
69 | }
70 | sentiments = SentimentValence(valence, sentiText, item, i, sentiments);
71 | }
72 |
73 | sentiments = ButCheck(wordsAndEmoticons, sentiments);
74 |
75 | return ScoreValence(sentiments, input);
76 | }
77 |
78 | private IList SentimentValence(double valence, SentiText sentiText, string item, int i, IList sentiments)
79 | {
80 | string itemLowerCase = item.ToLower();
81 | if (!Lexicon.ContainsKey(itemLowerCase))
82 | {
83 | sentiments.Add(valence);
84 | return sentiments;
85 | }
86 | bool isCapDiff = sentiText.IsCapDifferential;
87 | IList wordsAndEmoticons = sentiText.WordsAndEmoticons;
88 | valence = Lexicon[itemLowerCase];
89 | if (isCapDiff && item.IsUpper())
90 | {
91 | if (valence > 0)
92 | {
93 | valence += SentimentUtils.CIncr;
94 | }
95 | else
96 | {
97 | valence -= SentimentUtils.CIncr;
98 | }
99 | }
100 |
101 | for (int startI = 0; startI < 3; startI++)
102 | {
103 | if (i > startI && !Lexicon.ContainsKey(wordsAndEmoticons[i - (startI + 1)].ToLower()))
104 | {
105 | double s = SentimentUtils.ScalarIncDec(wordsAndEmoticons[i - (startI + 1)], valence, isCapDiff);
106 | if (startI == 1 && s != 0)
107 | s = s * 0.95;
108 | if (startI == 2 && s != 0)
109 | s = s * 0.9;
110 | valence = valence + s;
111 |
112 | valence = NeverCheck(valence, wordsAndEmoticons, startI, i);
113 |
114 | if (startI == 2)
115 | {
116 | valence = IdiomsCheck(valence, wordsAndEmoticons, i);
117 | }
118 |
119 | }
120 | }
121 |
122 | valence = LeastCheck(valence, wordsAndEmoticons, i);
123 | sentiments.Add(valence);
124 | return sentiments;
125 | }
126 |
127 | private IList ButCheck(IList wordsAndEmoticons, IList sentiments)
128 | {
129 | bool containsBUT = wordsAndEmoticons.Contains("BUT");
130 | bool containsbut = wordsAndEmoticons.Contains("but");
131 | if (!containsBUT && !containsbut)
132 | return sentiments;
133 |
134 | int butIndex = (containsBUT)
135 | ? wordsAndEmoticons.IndexOf("BUT")
136 | : wordsAndEmoticons.IndexOf("but");
137 |
138 | for (int i = 0; i < sentiments.Count; i++)
139 | {
140 | double sentiment = sentiments[i];
141 | if (i < butIndex)
142 | {
143 | sentiments.RemoveAt(i);
144 | sentiments.Insert(i,sentiment*0.5);
145 | }
146 | else if (i > butIndex)
147 | {
148 | sentiments.RemoveAt(i);
149 | sentiments.Insert(i, sentiment * 1.5);
150 | }
151 | }
152 | return sentiments;
153 | }
154 |
155 | private double LeastCheck(double valence, IList wordsAndEmoticons, int i)
156 | {
157 | if (i > 1 && !Lexicon.ContainsKey(wordsAndEmoticons[i - 1].ToLower()) &&
158 | wordsAndEmoticons[i - 1].ToLower() == "least")
159 | {
160 | if (wordsAndEmoticons[i - 2].ToLower() != "at" && wordsAndEmoticons[i - 2].ToLower() != "very")
161 | {
162 | valence = valence * SentimentUtils.NScalar;
163 | }
164 | }
165 | else if (i > 0 && !Lexicon.ContainsKey(wordsAndEmoticons[i-1].ToLower())
166 | && wordsAndEmoticons[i - 1].ToLower() == "least")
167 | {
168 | valence = valence * SentimentUtils.NScalar;
169 | }
170 |
171 | return valence;
172 | }
173 |
174 | private double NeverCheck(double valence, IList wordsAndEmoticons, int startI, int i)
175 | {
176 | if (startI == 0)
177 | {
178 | if (SentimentUtils.Negated(new List {wordsAndEmoticons[i - 1]}))
179 | valence = valence * SentimentUtils.NScalar;
180 | }
181 | if (startI == 1)
182 | {
183 | if (wordsAndEmoticons[i - 2] == "never" &&
184 | (wordsAndEmoticons[i - 1] == "so" || wordsAndEmoticons[i - 1] == "this"))
185 | {
186 | valence = valence * 1.5;
187 | }
188 | else if (SentimentUtils.Negated(new List {wordsAndEmoticons[i - (startI + 1)]}))
189 | {
190 | valence = valence * SentimentUtils.NScalar;
191 | }
192 | }
193 | if (startI == 2)
194 | {
195 | if (wordsAndEmoticons[i - 3] == "never"
196 | && (wordsAndEmoticons[i - 2] == "so" || wordsAndEmoticons[i - 2] == "this")
197 | || (wordsAndEmoticons[i - 1] == "so" || wordsAndEmoticons[i - 1] == "this"))
198 | {
199 | valence = valence * 1.25;
200 | }
201 | else if (SentimentUtils.Negated(new List { wordsAndEmoticons[i - (startI + 1)] }))
202 | {
203 | valence = valence * SentimentUtils.NScalar;
204 | }
205 | }
206 |
207 | return valence;
208 | }
209 |
210 | private double IdiomsCheck(double valence, IList wordsAndEmoticons, int i)
211 | {
212 | var oneZero = string.Concat(wordsAndEmoticons[i - 1], " ", wordsAndEmoticons[i]);
213 | var twoOneZero = string.Concat(wordsAndEmoticons[i - 2], " ", wordsAndEmoticons[i - 1], " ", wordsAndEmoticons[i]);
214 | var twoOne = string.Concat(wordsAndEmoticons[i - 2], " ", wordsAndEmoticons[i - 1]);
215 | var threeTwoOne = string.Concat(wordsAndEmoticons[i - 3], " ", wordsAndEmoticons[i - 2], " ", wordsAndEmoticons[i - 1]);
216 | var threeTwo = string.Concat(wordsAndEmoticons[i - 3], " ", wordsAndEmoticons[i - 2]);
217 |
218 | string[] sequences = {oneZero, twoOneZero, twoOne, threeTwoOne, threeTwo};
219 |
220 | foreach (var seq in sequences)
221 | {
222 | if (SentimentUtils.SpecialCaseIdioms.ContainsKey(seq))
223 | {
224 | valence = SentimentUtils.SpecialCaseIdioms[seq];
225 | break;
226 | }
227 | }
228 |
229 | if (wordsAndEmoticons.Count - 1 > i)
230 | {
231 | string zeroOne = string.Concat(wordsAndEmoticons[i], " ", wordsAndEmoticons[i + 1]);
232 | if (SentimentUtils.SpecialCaseIdioms.ContainsKey(zeroOne))
233 | {
234 | valence = SentimentUtils.SpecialCaseIdioms[zeroOne];
235 | }
236 | }
237 | if (wordsAndEmoticons.Count - 1 > i + 1)
238 | {
239 | string zeroOneTwo = string.Concat(wordsAndEmoticons[i], " ", wordsAndEmoticons[i + 1], " ", wordsAndEmoticons[i + 2]);
240 | if (SentimentUtils.SpecialCaseIdioms.ContainsKey(zeroOneTwo))
241 | {
242 | valence = SentimentUtils.SpecialCaseIdioms[zeroOneTwo];
243 | }
244 | }
245 | if (SentimentUtils.BoosterDict.ContainsKey(threeTwo) || SentimentUtils.BoosterDict.ContainsKey(twoOne))
246 | {
247 | valence += SentimentUtils.BDecr;
248 | }
249 | return valence;
250 | }
251 |
252 | private double PunctuationEmphasis(string text)
253 | {
254 | return AmplifyExclamation(text) + AmplifyQuestion(text);
255 | }
256 |
257 | private double AmplifyExclamation(string text)
258 | {
259 | int epCount = text.Count(x => x == '!');
260 |
261 | if (epCount > 4)
262 | epCount = 4;
263 |
264 | return epCount * ExclIncr;
265 | }
266 |
267 | private static double AmplifyQuestion(string text)
268 | {
269 | int qmCount = text.Count(x => x == '?');
270 |
271 | if (qmCount < 1)
272 | return 0;
273 |
274 | if (qmCount <= 3)
275 | return qmCount * QuesIncrSmall;
276 |
277 | return QuesIncrLarge;
278 | }
279 |
280 | private static SiftSentiments SiftSentimentScores(IList sentiments)
281 | {
282 | SiftSentiments siftSentiments = new SiftSentiments();
283 |
284 | foreach (var sentiment in sentiments)
285 | {
286 | if (sentiment > 0)
287 | siftSentiments.PosSum += (sentiment + 1); //1 compensates for neutrals
288 |
289 | if (sentiment < 0)
290 | siftSentiments.NegSum += (sentiment - 1);
291 |
292 | if (sentiment == 0)
293 | siftSentiments.NeuCount++;
294 | }
295 | return siftSentiments;
296 | }
297 |
298 | private SentimentAnalysisResults ScoreValence(IList sentiments, string text)
299 | {
300 | if (sentiments.Count == 0)
301 | return new SentimentAnalysisResults(); //will return with all 0
302 |
303 | double sum = sentiments.Sum();
304 | double puncAmplifier = PunctuationEmphasis(text);
305 |
306 | sum += Math.Sign(sum) * puncAmplifier;
307 |
308 | double compound = SentimentUtils.Normalize(sum);
309 | SiftSentiments sifted = SiftSentimentScores(sentiments);
310 |
311 | if (sifted.PosSum > Math.Abs(sifted.NegSum))
312 | {
313 | sifted.PosSum += puncAmplifier;
314 | }
315 | else if (sifted.PosSum < Math.Abs(sifted.NegSum))
316 | {
317 | sifted.NegSum -= puncAmplifier;
318 | }
319 |
320 | double total = sifted.PosSum + Math.Abs(sifted.NegSum) + sifted.NeuCount;
321 | return new SentimentAnalysisResults
322 | {
323 | Compound = Math.Round(compound,4),
324 | Positive = Math.Round(Math.Abs(sifted.PosSum /total), 3),
325 | Negative = Math.Round(Math.Abs(sifted.NegSum/total),3),
326 | Neutral = Math.Round(Math.Abs(sifted.NeuCount/total), 3)
327 | };
328 | }
329 |
330 | private class SiftSentiments
331 | {
332 | public double PosSum { get; set; }
333 | public double NegSum { get; set; }
334 | public int NeuCount { get; set; }
335 | }
336 | }
337 |
338 | }
339 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/SentimentUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace VaderSharp
5 | {
6 | internal static class SentimentUtils
7 | {
8 | #region Constants
9 |
10 | public const double BIncr = 0.293;
11 | public const double BDecr = -0.293;
12 | public const double CIncr = 0.733;
13 | public const double NScalar = -0.74;
14 |
15 | public static readonly string[] PuncList =
16 | {
17 | ".", "!", "?", ",", ";", ":", "-", "'", "\"","!!", "!!!",
18 | "??", "???", "?!?", "!?!", "?!?!", "!?!?"
19 | };
20 |
21 | public static readonly string[] Negate =
22 | {
23 | "aint", "arent", "cannot", "cant", "couldnt", "darent", "didnt", "doesnt",
24 | "ain't", "aren't", "can't", "couldn't", "daren't", "didn't", "doesn't",
25 | "dont", "hadnt", "hasnt", "havent", "isnt", "mightnt", "mustnt", "neither",
26 | "don't", "hadn't", "hasn't", "haven't", "isn't", "mightn't", "mustn't",
27 | "neednt", "needn't", "never", "none", "nope", "nor", "not", "nothing", "nowhere",
28 | "oughtnt", "shant", "shouldnt", "uhuh", "wasnt", "werent",
29 | "oughtn't", "shan't", "shouldn't", "uh-uh", "wasn't", "weren't",
30 | "without", "wont", "wouldnt", "won't", "wouldn't", "rarely", "seldom", "despite"
31 | };
32 |
33 | public static readonly Dictionary BoosterDict = new Dictionary
34 | {
35 | {"absolutely", BIncr},
36 | {"amazingly", BIncr},
37 | {"awfully", BIncr},
38 | {"completely", BIncr},
39 | {"considerably", BIncr},
40 | {"decidedly", BIncr},
41 | {"deeply", BIncr},
42 | {"effing", BIncr},
43 | {"enormously", BIncr},
44 | {"entirely", BIncr},
45 | {"especially", BIncr},
46 | {"exceptionally", BIncr},
47 | {"extremely", BIncr},
48 | {"fabulously", BIncr},
49 | {"flipping", BIncr },
50 | {"flippin", BIncr},
51 | {"fricking", BIncr},
52 | {"frickin", BIncr},
53 | {"frigging", BIncr},
54 | {"friggin", BIncr},
55 | {"fully", BIncr},
56 | {"fucking", BIncr},
57 | {"greatly", BIncr},
58 | {"hella", BIncr},
59 | {"highly", BIncr},
60 | {"hugely", BIncr},
61 | {"incredibly", BIncr},
62 | {"intensely", BIncr},
63 | {"majorly", BIncr},
64 | {"more", BIncr},
65 | {"most", BIncr},
66 | {"particularly", BIncr},
67 | {"purely", BIncr},
68 | {"quite", BIncr},
69 | {"really", BIncr},
70 | {"remarkably", BIncr},
71 | {"so", BIncr},
72 | {"substantially", BIncr},
73 | {"thoroughly", BIncr},
74 | {"totally", BIncr},
75 | {"tremendously", BIncr},
76 | {"uber", BIncr},
77 | {"unbelievably", BIncr},
78 | {"unusually", BIncr},
79 | {"utterly", BIncr},
80 | {"very", BIncr},
81 | { "almost", BDecr},
82 | { "barely", BDecr},
83 | { "hardly", BDecr},
84 | { "just enough", BDecr},
85 | { "kind of", BDecr},
86 | { "kinda", BDecr},
87 | { "kindof", BDecr},
88 | { "kind-of", BDecr},
89 | { "less", BDecr},
90 | { "little", BDecr},
91 | { "marginally", BDecr},
92 | { "occasionally", BDecr},
93 | { "partly", BDecr},
94 | { "scarcely", BDecr},
95 | { "slightly", BDecr},
96 | { "somewhat", BDecr},
97 | {"sort of", BDecr},
98 | { "sorta", BDecr},
99 | { "sortof", BDecr},
100 | { "sort-of", BDecr}
101 | };
102 |
103 | public static readonly Dictionary SpecialCaseIdioms = new Dictionary
104 | {
105 | {"the shit", 3},
106 | { "the bomb", 3},
107 | { "bad ass", 1.5},
108 | { "yeah right", -2},
109 | { "cut the mustard", 2},
110 | { "kiss of death", -1.5},
111 | { "hand to mouth", -2}
112 | };
113 |
114 | #endregion
115 |
116 | #region Util static methods
117 | ///
118 | /// Determine if input contains negation words
119 | ///
120 | ///
121 | ///
122 | ///
123 | public static bool Negated(IList inputWords, bool includenT = true)
124 | {
125 | foreach (var word in Negate)
126 | {
127 | if (inputWords.Contains(word))
128 | return true;
129 | }
130 |
131 | if (includenT)
132 | {
133 | foreach (var word in inputWords)
134 | {
135 | if (word.Contains(@"n't"))
136 | return true;
137 | }
138 | }
139 |
140 | if (inputWords.Contains("least"))
141 | {
142 | int i = inputWords.IndexOf("least");
143 | if (i > 0 && inputWords[i - 1] != "at")
144 | return true;
145 | }
146 |
147 | return false;
148 | }
149 |
150 | ///
151 | /// Normalizes score to be between -1 and 1
152 | ///
153 | ///
154 | ///
155 | ///
156 | public static double Normalize(double score, double alpha = 15)
157 | {
158 | double normScore = score / Math.Sqrt(score * score + alpha);
159 |
160 | if (normScore < -1.0)
161 | return -1.0;
162 |
163 | if (normScore > 1.0)
164 | return 1.0;
165 |
166 | return normScore;
167 | }
168 |
169 | ///
170 | /// Checks whether some but not all of words in input are ALL CAPS
171 | ///
172 | ///
173 | ///
174 | public static bool AllCapDifferential(IList words)
175 | {
176 | int allCapWords = 0;
177 |
178 | foreach (var word in words)
179 | {
180 | if (word.IsUpper())
181 | allCapWords++;
182 | }
183 |
184 | int capDifferential = words.Count - allCapWords;
185 | return (capDifferential > 0 && capDifferential < words.Count);
186 | }
187 |
188 | ///
189 | /// Check if preceding words increase, decrease or negate the valence
190 | ///
191 | ///
192 | ///
193 | ///
194 | ///
195 | public static double ScalarIncDec(string word, double valence, bool isCapDiff)
196 | {
197 | string wordLower = word.ToLower();
198 | if (!BoosterDict.ContainsKey(wordLower))
199 | return 0.0;
200 |
201 | double scalar = BoosterDict[wordLower];
202 | if (valence < 0)
203 | scalar *= -1;
204 |
205 | if (word.IsUpper() && isCapDiff)
206 | {
207 | scalar += (valence > 0) ? CIncr : -CIncr;
208 | }
209 |
210 | return scalar;
211 | }
212 |
213 | #endregion
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharp/VaderSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A sentiment analysis algorithm in C#.
5 | Sentiment analysis with C# using the VADER algorithm.
6 | 2017
7 | VaderSharp
8 | 0.0.0
9 |
10 | net35;netstandard1.3
11 | true
12 | VaderSharp
13 | CodingUpAStorm.VaderSharp
14 | sentiment;analysis;vader;valence;text;algorithm
15 | https://github.com/codingupastorm/vadersharp
16 | https://github.com/codingupastorm/vadersharp/blob/master/LICENSE
17 | git
18 | https://github.com/codingupastorm/vadersharp
19 | $(PackageTargetFallback);dnxcore50
20 | false
21 | false
22 |
23 |
24 |
25 | netstandard1.3;net35
26 | 0.0.0.0
27 | 0.0.0.0
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | $(DefineConstants);NET_35
36 |
37 |
38 |
39 | $(DefineConstants);NET_STANDARD
40 |
41 |
42 |
43 |
44 | C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client
45 |
46 |
47 |
48 | bin\Release\netstandard1.3\VaderSharp.xml
49 |
50 |
51 |
52 |
53 | Never
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharpTestCore/SentimentTest.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using VaderSharp;
5 |
6 | namespace VaderSharpTestCore
7 | {
8 | [TestClass]
9 | public class SentimentTest
10 | {
11 | [TestMethod]
12 | public void MatchPythonTest()
13 | {
14 | SentimentIntensityAnalyzer analyzer = new SentimentIntensityAnalyzer();
15 |
16 | var standardGoodTest = analyzer.PolarityScores("VADER is smart, handsome, and funny.");
17 | Assert.AreEqual(standardGoodTest.Negative, 0);
18 | Assert.AreEqual(standardGoodTest.Neutral, 0.254);
19 | Assert.AreEqual(standardGoodTest.Positive, 0.746);
20 | Assert.AreEqual(standardGoodTest.Compound, 0.8316);
21 |
22 | var kindOfTest = analyzer.PolarityScores("The book was kind of good.");
23 | Assert.AreEqual(kindOfTest.Negative, 0);
24 | Assert.AreEqual(kindOfTest.Neutral, 0.657);
25 | Assert.AreEqual(kindOfTest.Positive, 0.343);
26 | Assert.AreEqual(kindOfTest.Compound, 0.3832);
27 |
28 | var complexTest =
29 | analyzer.PolarityScores(
30 | "The plot was good, but the characters are uncompelling and the dialog is not great.");
31 | Assert.AreEqual(complexTest.Negative, 0.327);
32 | Assert.AreEqual(complexTest.Neutral, 0.579);
33 | Assert.AreEqual(complexTest.Positive, 0.094);
34 | Assert.AreEqual(complexTest.Compound, -0.7042);
35 |
36 | }
37 |
38 | [TestMethod]
39 | public void TestConfigStore()
40 | {
41 | ConfigStore cfg = ConfigStore.CreateConfig("en-gb");
42 | var negations = cfg.Negations;
43 | string[] Negate =
44 | {
45 | "aint", "arent", "cannot", "cant", "couldnt", "darent", "didnt", "doesnt",
46 | "ain't", "aren't", "can't", "couldn't", "daren't", "didn't", "doesn't",
47 | "dont", "hadnt", "hasnt", "havent", "isnt", "mightnt", "mustnt", "neither",
48 | "don't", "hadn't", "hasn't", "haven't", "isn't", "mightn't", "mustn't",
49 | "neednt", "needn't", "never", "none", "nope", "nor", "not", "nothing", "nowhere",
50 | "oughtnt", "shant", "shouldnt", "uhuh", "wasnt", "werent",
51 | "oughtn't", "shan't", "shouldn't", "uh-uh", "wasn't", "weren't",
52 | "without", "wont", "wouldnt", "won't", "wouldn't", "rarely", "seldom", "despite"
53 | };
54 | bool isExisting;
55 | Assert.AreEqual(negations.Length, Negate.Length);
56 |
57 | foreach (var a in negations)
58 | {
59 | isExisting = false;
60 | foreach (var b in Negate)
61 | {
62 | if(a.Equals(b))
63 | {
64 | isExisting = true;
65 | break;
66 | }
67 | }
68 | Assert.IsTrue(isExisting);
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/VaderSharp/VaderSharpTestCore/VaderSharpTestCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp1.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------