vignettes/MultipleAnalyses.Rmd
MultipleAnalyses.Rmd
In this vignette we focus on running several different analyses on several exposure-outcome pairs This can be useful when we want to explore the sensitivity to analyses choices, include controls, or run an experiment similar to the OMOP experiment to empirically identify the optimal analysis choices for a particular research question.
This vignette assumes you are already familiar with the
SelfControlledCaseSeries
package and are able to perform
single studies. We will walk through all the steps needed to perform an
exemplar set of analyses, and we have selected the well-studied topic of
the effect of nonsteroidal anti-inflammatory drugs (NSAIDs) on
gastrointestinal (GI) bleeding-related hospitalization. For simplicity,
we focus on one NSAID: diclofenac. We will execute various variations of
an analysis for the primary exposure pair and a large set of negative
control exposures.
The general approach to running a set of analyses is that you specify all the function arguments of the functions you would normally call, and create sets of these function arguments. The final outcome models as well as intermediate data objects will all be saved to disk for later extraction.
An analysis will be executed by calling these functions in sequence:
When you provide several analyses to the
SelfControlledCaseSeries
package, it will determine whether
any of the analyses and exposure-outcome pairs have anything in common,
and will take advantage of this fact. For example, if we specify several
exposure-outcome pairs with the same outcome, the data for the outcome
will be extracted only once.
The function arguments you need to define have been divided into four groups:
SelfControlledCaseSeries
package, such as the
SccsIntervalData
argument of the
createSccsIntervalData
function. These cannot be specified
by the user.We need to tell R how to connect to the server where the data are.
SelfControlledCaseSeries
uses the
DatabaseConnector
package, which provides the
createConnectionDetails
function. Type
?createConnectionDetails
for the specific settings required
for the various database management systems (DBMS). For example, one
might connect to a PostgreSQL database using this code:
connectionDetails <- createConnectionDetails(
dbms = "postgresql",
server = "localhost/ohdsi",
user = "joe",
password = "supersecret"
)
outputFolder <- "s:/temp/sccsVignette2"
cdmDatabaseSchema <- "my_cdm_data"
cohortDatabaseSchema <- "my_cohorts"
options(sqlRenderTempEmulationSchema = NULL)
cdmVersion <- "5"
The last three lines define the cdmDatabaseSchema
and
cohortDatabaseSchema
variables, as well as the CDM version.
We’ll use these later to tell R where the data in CDM format live, where
we want to store the (outcome) cohorts, and what version CDM is used.
Note that for Microsoft SQL Server, databaseschemas need to specify both
the database and the schema, so for example
cdmDatabaseSchema <- "my_cdm_data.dbo"
.
We also need to prepare our exposures and outcomes of interest. The drug_era table in the OMOP Common Data Model already contains pre-specified cohorts of users at the ingredient level, so we will use that for the exposures. For the outcomes, we want to restrict our analysis only to those events that are recorded in an inpatient setting, so we will need to create a custom cohort table. For this example, we are only interested in GI bleed (concept ID 192671) .
We create a text file called vignette.sql with the following content:
/***********************************
File vignette.sql
***********************************/
DROP TABLE IF EXISTS @cohortDatabaseSchema.@outcomeTable;
SELECT 1 AS cohort_definition_id,
AS cohort_start_date,
condition_start_date AS cohort_end_date,
condition_end_date AS subject_id
condition_occurrence.person_id INTO @cohortDatabaseSchema.@outcomeTable
FROM @cdmDatabaseSchema.condition_occurrence
INNER JOIN @cdmDatabaseSchema.visit_occurrence
ON condition_occurrence.visit_occurrence_id = visit_occurrence.visit_occurrence_id
WHERE condition_concept_id IN (
SELECT descendant_concept_id
FROM @cdmDatabaseSchema.concept_ancestor
WHERE ancestor_concept_id = 192671 -- GI - Gastrointestinal haemorrhage
)AND visit_occurrence.visit_concept_id IN (9201, 9203);
This is parameterized SQL which can be used by the
SqlRender
package. We use parameterized SQL so we do not
have to pre-specify the names of the CDM and result schemas. That way,
if we want to run the SQL on a different schema, we only need to change
the parameter values; we do not have to change the SQL code. By also
making use of translation functionality in SqlRender
, we
can make sure the SQL code can be run in many different
environments.
library(SqlRender)
sql <- readSql("vignette.sql")
sql <- render(sql,
cdmDatabaseSchema = cdmDatabaseSchema,
cohortDatabaseSchema = cohortDatabaseSchema
)
sql <- translate(sql, targetDialect = connectionDetails$dbms)
connection <- connect(connectionDetails)
executeSql(connection, sql)
In this code, we first read the SQL from the file into memory. In the
next line, we replace the two parameter names with the actual values. We
then translate the SQL into the dialect appropriate for the DBMS we
already specified in the connectionDetails
. Next, we
connect to the server, and submit the rendered and translated SQL.
The first group of arguments define the exposures and outcome. Here we demonstrate how to create an exposures-outcome set:
diclofenac <- 1124300
negativeControls <- c(
705178, 705944, 710650, 714785, 719174, 719311, 735340, 742185,
780369, 781182, 924724, 990760, 1110942, 1111706, 1136601,
1317967, 1501309, 1505346, 1551673, 1560278, 1584910, 19010309,
40163731
)
exposuresOutcomeList <- list()
exposuresOutcomeList[[1]] <- createExposuresOutcome(
outcomeId = 1,
exposures = list(createExposure(exposureId = diclofenac))
)
for (exposureId in c(negativeControls)) {
exposuresOutcome <- createExposuresOutcome(
outcomeId = 1,
exposures = list(createExposure(exposureId = exposureId, trueEffectSize = 1))
)
exposuresOutcomeList[[length(exposuresOutcomeList) + 1]] <- exposuresOutcome
}
We defined the outcome of interest to be the cohort with ID 1 we
defined in the SQL above. The exposures include diclofenac (concept ID
1124300) and a large number of negative control exposures. Note that for
the negative controls we specify trueEffectSize = 1
.
A convenient way to save exposuresOutcomeList
to file is
by using the saveExposuresOutcomeList
function, and we can
load it again using the loadExposuresOutcomeList
function.
The second group of arguments are not specific to a hypothesis of
interest, and comprise the majority of arguments. For each function that
will be called during the execution of the analyses, a companion
function is available that has (almost) the same arguments. For example,
for the fitSccsModel()
function there is the
createFitSccsModelArgs()
function. These companion
functions can be used to create the arguments to be used during
execution:
getDbSccsDataArgs <- createGetDbSccsDataArgs(
useCustomCovariates = FALSE,
deleteCovariatesSmallCount = 100,
exposureIds = c(),
maxCasesPerOutcome = 100000
)
createStudyPopulationArgs <- createCreateStudyPopulationArgs(
naivePeriod = 180,
firstOutcomeOnly = FALSE
)
covarExposureOfInt <- createEraCovariateSettings(
label = "Exposure of interest",
includeEraIds = "exposureId",
start = 1,
end = 0,
endAnchor = "era end",
profileLikelihood = TRUE,
exposureOfInterest = TRUE
)
createSccsIntervalDataArgs1 <- createCreateSccsIntervalDataArgs(
eraCovariateSettings = covarExposureOfInt
)
fitSccsModelArgs <- createFitSccsModelArgs()
Any argument that is not explicitly specified by the user will assume
the default value specified in the function. Note that for several
arguments for concept or cohort definition IDs we can use the
exposureIdRef
(default = "exposureId"
) in the
Exposure
objects that we used in
createExposuresOutcome()
. In this case, we defined the
argument includeEraIds
to get the value of the
exposureId
variable, meaning it will take the value of the
diclofenac concept ID, or any of the negative control IDs. Also note
that we set exposureOfInterest = TRUE
, which will cause the
estimate for this covariate to be included in the result summary later
on.
We can now combine the arguments for the various functions into a single analysis:
sccsAnalysis1 <- createSccsAnalysis(
analysisId = 1,
description = "Simplest model",
getDbSccsDataArgs = getDbSccsDataArgs,
createStudyPopulationArgs = createStudyPopulationArgs,
createIntervalDataArgs = createSccsIntervalDataArgs1,
fitSccsModelArgs = fitSccsModelArgs
)
Note that we have assigned an analysis ID (1) to this set of arguments. We can use this later to link the results back to this specific set of choices. We also include a short description of the analysis.
We can easily create more analyses, for example by including adjustments for age and seasonality, or for including other drugs in the model:
ppis <- c(911735, 929887, 923645, 904453, 948078, 19039926)
covarPreExp <- createEraCovariateSettings(
label = "Pre-exposure",
includeEraIds = "exposureId",
start = -30,
end = -1,
endAnchor = "era start"
)
covarProphylactics <- createEraCovariateSettings(
label = "Prophylactics",
includeEraIds = ppis,
start = 1,
end = 0,
endAnchor = "era end"
)
createSccsIntervalDataArgs2 <- createCreateSccsIntervalDataArgs(
eraCovariateSettings = list(
covarExposureOfInt,
covarPreExp,
covarProphylactics
)
)
sccsAnalysis2 <- createSccsAnalysis(
analysisId = 2,
description = "Including prophylactics and pre-exposure",
getDbSccsDataArgs = getDbSccsDataArgs,
createStudyPopulationArgs = createStudyPopulationArgs,
createIntervalDataArgs = createSccsIntervalDataArgs2,
fitSccsModelArgs = fitSccsModelArgs
)
seasonalitySettings <- createSeasonalityCovariateSettings(seasonKnots = 5)
calendarTimeSettings <- createCalendarTimeCovariateSettings(calendarTimeKnots = 5)
createSccsIntervalDataArgs3 <- createCreateSccsIntervalDataArgs(
eraCovariateSettings = list(
covarExposureOfInt,
covarPreExp,
covarProphylactics
),
seasonalityCovariateSettings = seasonalitySettings,
calendarTimeCovariateSettings = calendarTimeSettings
)
sccsAnalysis3 <- createSccsAnalysis(
analysisId = 3,
description = "Including prophylactics, season, calendar time, and pre-exposure",
getDbSccsDataArgs = getDbSccsDataArgs,
createStudyPopulationArgs = createStudyPopulationArgs,
createIntervalDataArgs = createSccsIntervalDataArgs3,
fitSccsModelArgs = fitSccsModelArgs
)
covarAllDrugs <- createEraCovariateSettings(
label = "Other exposures",
excludeEraIds = "exposureId",
stratifyById = TRUE,
start = 1,
end = 0,
endAnchor = "era end",
allowRegularization = TRUE
)
createSccsIntervalDataArgs4 <- createCreateSccsIntervalDataArgs(
eraCovariateSettings = list(
covarExposureOfInt,
covarPreExp,
covarAllDrugs
),
seasonalityCovariateSettings = seasonalitySettings,
calendarTimeCovariateSettings = calendarTimeSettings
)
sccsAnalysis4 <- createSccsAnalysis(
analysisId = 4,
description = "Including all other drugs",
getDbSccsDataArgs = getDbSccsDataArgs,
createStudyPopulationArgs = createStudyPopulationArgs,
createIntervalDataArgs = createSccsIntervalDataArgs4,
fitSccsModelArgs = fitSccsModelArgs
)
createSccsIntervalDataArgs5 <- createCreateSccsIntervalDataArgs(
eraCovariateSettings = list(
covarExposureOfInt,
covarPreExp,
covarProphylactics
),
eventDependentObservation = TRUE
)
sccsAnalysis5 <- createSccsAnalysis(
analysisId = 5,
description = "Adjusting for event-dependent obs. end",
getDbSccsDataArgs = getDbSccsDataArgs,
createStudyPopulationArgs = createStudyPopulationArgs,
createIntervalDataArgs = createSccsIntervalDataArgs5,
fitSccsModelArgs = fitSccsModelArgs
)
These analyses can be combined in a list:
sccsAnalysisList <- list(
sccsAnalysis1,
sccsAnalysis2,
sccsAnalysis3,
sccsAnalysis4,
sccsAnalysis5
)
A convenient way to save sccsAnalysisList
to file is by
using the saveSccsAnalysisList
function, and we can load it
again using the loadSccsAnalysisList
function.
We can now run the analyses against the hypotheses of interest using
the runScsAnalyses()
function. This function will run all
specified analyses against all hypotheses of interest, meaning that the
total number of outcome models is
length(sccsAnalysisList) * length(exposuresOutcomeList)
.
(If we want, we can skip some of these combinations using the
analysesToExclude
argument.)
multiThreadingSettings <- createDefaultSccsMultiThreadingSettings(
parallel::detectCores() - 1
)
referenceTable <- runSccsAnalyses(
connectionDetails = connectionDetails,
cdmDatabaseSchema = cdmDatabaseSchema,
exposureDatabaseSchema = cdmDatabaseSchema,
exposureTable = "drug_era",
outcomeDatabaseSchema = cohortDatabaseSchema,
outcomeTable = outcomeTable,
cdmVersion = cdmVersion,
outputFolder = outputFolder,
combineDataFetchAcrossOutcomes = TRUE,
exposuresOutcomeList = exposuresOutcomeList,
sccsAnalysisList = sccsAnalysisList,
sccsMultiThreadingSettings = multiThreadingSettings
)
In the code above, we first specify how many parallel threads
SelfControlledCaseSeires
can use. Many of the computations
can be computed in parallel, and providing more than one CPU core can
greatly speed up the computation. Here we specify
SelfControlledCaseSeires
can use all the CPU cores detected
in the system (using the parallel::detectCores()
function).
We call runSccsAnalyses
, providing the arguments for
connecting to the database, which schemas and tables to use, as well as
the analyses and hypotheses of interest. The outputFolder
specifies where the outcome models and intermediate files will be
written.
If for some reason the execution was interrupted, you can restart by
re-issuing the runSccsAnalyses()
command. Any intermediate
and final products that have already been completed and written to disk
will be skipped.
The result of the runSccsAnalyses()
is a data frame with
one row per exposures-outcome-analysis combination, so one row per
fitted SCCS model. It provides the file names of the intermediate and
end-result files that were constructed. For example, we can retrieve the
fitted model for the combination of our drug of interest, outcome, and
first analysis:
sccsModelFile <- result$sccsModelFile[result$exposureId == 1124300 &
result$outcomeId == 1 &
result$analysisId == 1]
sccsModel <- readRDS(file.path(outputFolder, sccsModelFile))
sccsModel
## SccsModel object
##
## Outcome ID: 1
##
## Outcome count:
## outcomeSubjects outcomeEvents outcomeObsPeriods observedDays
## 1 8510 24996 8512 21374750
##
## Estimates:
## # A tibble: 1 x 7
## Name ID Estimate LB95CI UB95CI LogRr SeLogRr
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Exposure of interest 1000 1.10 1.04 1.17 0.0985 0.0299
Note that some of the file names will appear several times in the
table. In our example all analysis share the same sccsData
object.
We can always retrieve the file reference table again using the
getFileReference()
function:
result <- getFileReference(outputFolder)
We can get a summary of the results using
getResultsSummary()
:
resultsSum <- getResultsSummary(outputFolder)
head(resultsSum)
## # A tibble: 6 x 30
## exposuresOutcomeSetId outcomeId analysisId covariateAnalysisId covariateId
## <dbl> <dbl> <int> <dbl> <dbl>
## 1 1 1 1 1 1000
## 2 2 1 1 1 1000
## 3 3 1 1 1 1000
## 4 4 1 1 1 1000
## 5 5 1 1 1 1000
## 6 6 1 1 1 1000
## # i 25 more variables: covariateName <chr>, eraId <dbl>, outcomeSubjects <dbl>,
## # outcomeEvents <dbl>, outcomeObservationPeriods <dbl>, observedDays <dbl>,
## # covariateSubjects <dbl>, covariateDays <dbl>, covariateEras <dbl>,
## # covariateOutcomes <dbl>, rr <dbl>, ci95Lb <dbl>, ci95Ub <dbl>, p <dbl>,
## # logRr <dbl>, seLogRr <dbl>, llr <dbl>, trueEffectSize <int>,
## # calibratedRr <dbl>, calibratedCi95Lb <dbl>, calibratedCi95Ub <dbl>,
## # calibratedP <dbl>, calibratedLogRr <dbl>, calibratedSeLogRr <dbl>, ...
This tells us, per exposure-outcome-analysis combination (so possible
multiple rows per SCCS model) the estimated relative risk and 95%
confidence interval, as well as the number of subjects (cases) and the
number of events observed for those subjects. The only covariates
included in this summary are those we marked with
exposureOfInterest = TRUE
when calling
createEraCovariateSettings()
earlier.
Now that we have produced estimates for all outcomes including our negative controls, we can perform empirical calibration to estimate the bias of the various analyses included in our study. We will create the calibration effect plots for every analysis ID. In each plot, the blue dots represent our negative control exposures, and the yellow diamond represents our exposure of interest: diclofenac. An unbiased, well-calibrated analysis should have 95% of the negative controls between the dashed lines (ie. 95% should have p > .05).
install.packages("EmpiricalCalibration")
library(EmpiricalCalibration)
# Analysis 1: Simplest model
negCons <- resultsSum[resultsSum$analysisId == 1 & resultsSum$eraId != 1124300, ]
ei <- resultsSum[resultsSum$analysisId == 1 & resultsSum$eraId == 1124300, ]
null <- fitNull(
negCons$logRr,
negCons$seLogRr
)
plotCalibrationEffect(
logRrNegatives = negCons$logRr,
seLogRrNegatives = negCons$seLogRr,
logRrPositives = ei$logRr,
seLogRrPositives = ei$seLogRr,
null
)
# Analysis 2: Including prophylactics and pre-exposure
negCons <- resultsSum[resultsSum$analysisId == 2 & resultsSum$eraId != 1124300, ]
ei <- resultsSum[resultsSum$analysisId == 2 & resultsSum$eraId == 1124300, ]
null <- fitNull(
negCons$logRr,
negCons$seLogRr
)
plotCalibrationEffect(
logRrNegatives = negCons$logRr,
seLogRrNegatives = negCons$seLogRr,
logRrPositives = ei$logRr,
seLogRrPositives = ei$seLogRr,
null
)
# Analysis 3: Including prophylactics, season, calendar time, and pre-exposure
negCons <- resultsSum[resultsSum$analysisId == 3 & resultsSum$eraId != 1124300, ]
ei <- resultsSum[resultsSum$analysisId == 3 & resultsSum$eraId == 1124300, ]
null <- fitNull(
negCons$logRr,
negCons$seLogRr
)
plotCalibrationEffect(
logRrNegatives = negCons$logRr,
seLogRrNegatives = negCons$seLogRr,
logRrPositives = ei$logRr,
seLogRrPositives = ei$seLogRr,
null
)
# Analysis 4: Including all other drugs
negCons <- resultsSum[resultsSum$analysisId == 4 & resultsSum$eraId != 1124300, ]
ei <- resultsSum[resultsSum$analysisId == 4 & resultsSum$eraId == 1124300, ]
null <- fitNull(
negCons$logRr,
negCons$seLogRr
)
plotCalibrationEffect(
logRrNegatives = negCons$logRr,
seLogRrNegatives = negCons$seLogRr,
logRrPositives = ei$logRr,
seLogRrPositives = ei$seLogRr,
null
)
# Analysis 5: Adjusting for event-dependent obs. end
negCons <- resultsSum[resultsSum$analysisId == 4 & resultsSum$eraId != 1124300, ]
ei <- resultsSum[resultsSum$analysisId == 4 & resultsSum$eraId == 1124300, ]
null <- fitNull(
negCons$logRr,
negCons$seLogRr
)
plotCalibrationEffect(
logRrNegatives = negCons$logRr,
seLogRrNegatives = negCons$seLogRr,
logRrPositives = ei$logRr,
seLogRrPositives = ei$seLogRr,
null
)
Considerable work has been dedicated to provide the
SelfControlledCaseSeries
package.
citation("SelfControlledCaseSeries")
##
## To cite package 'SelfControlledCaseSeries' in publications use:
##
## Schuemie M, Ryan P, Shaddox T, Suchard M (2023).
## _SelfControlledCaseSeries: Self-Controlled Case Series_. R package
## version 4.2.0, <https://github.com/OHDSI/SelfControlledCaseSeries>.
##
## A BibTeX entry for LaTeX users is
##
## @Manual{,
## title = {SelfControlledCaseSeries: Self-Controlled Case Series},
## author = {Martijn Schuemie and Patrick Ryan and Trevor Shaddox and Marc Suchard},
## year = {2023},
## note = {R package version 4.2.0},
## url = {https://github.com/OHDSI/SelfControlledCaseSeries},
## }
Further, SelfControlledCaseSeries
makes extensive use of
the Cyclops
package.
citation("Cyclops")
##
## To cite Cyclops in publications use:
##
## Suchard MA, Simpson SE, Zorych I, Ryan P, Madigan D (2013). "Massive
## parallelization of serial inference algorithms for complex
## generalized linear models." _ACM Transactions on Modeling and
## Computer Simulation_, *23*, 10.
## <https://dl.acm.org/doi/10.1145/2414416.2414791>.
##
## A BibTeX entry for LaTeX users is
##
## @Article{,
## author = {M. A. Suchard and S. E. Simpson and I. Zorych and P. Ryan and D. Madigan},
## title = {Massive parallelization of serial inference algorithms for complex generalized linear models},
## journal = {ACM Transactions on Modeling and Computer Simulation},
## volume = {23},
## pages = {10},
## year = {2013},
## url = {https://dl.acm.org/doi/10.1145/2414416.2414791},
## }
This work is supported in part through the National Science Foundation grant IIS 1251151.


Fall 2021: CX4640 Numerical Analysis 1
Course Description
Introduction to fundamental algorithms and analysis of numerical methods commonly used by scientists, mathematicians, and engineers.
Prerequisites
- Linear and Discrete Mathematics (MATH 2602)
- Differential Equations (MATH 2403/2413)
- Intermediate-level expertise of Python, Matlab, Julia, Mathematica, C/C++, Fortran, or similar
Topics
- Finite precision computation, sources of error, stability, conditioning
- Linear systems of equations
- Linear least squares
- Eigenvalue problems
- Solution of nonlinear equations
- Optimization
- Interpolation and approximation
- Numerical differentiation and integration
- Numerical solution of ordinary differential equations
- Numerical solution of partial differential equations
Grading
Breakdown
- 5% Participation
- 10% Homework
- 25% Midterm 1 (take-home)
- 25% Midterm 2 (take-home)
- 35% Final project (take-home)
Letters
Your final grade will be assigned as a letter grade according to the following scale.
- A: 90-100%
- B: 80-89%
- C: 70-79%
- D: 60-69%
- F: 0-59%
Textbook
Michael Heath, Scientific Computing: An Introductory Survey (Revised Second Edition)
Course expectations and guidelines
University policy
Georgia Tech aims to cultivate a community based on trust, academic integrity, and honor.
Students are expected to act according to the highest ethical standards.
Any student suspected of cheating or plagiarizing on a quiz, exam, or assignment will be reported to the Office of Student Integrity, which will investigate the incident and identify the appropriate penalty for violations.
Collaboration policy
- Homework and midterms: These are to be completed on your own.
No collaboration is acceptable.
- Final project: Intellectual collaboration is acceptable, though all “hands on” work is to be your own.
This includes your code and your report.
Late assignments
Assignments submitted late are subject to a 20% penalty per day late.
Accommodations
If you are a student with learning needs that require special accommodation, contact the Office of Disability Services at<PHONE_NUMBER> or their website, as soon as possible, to discuss your needs and to obtain an accommodations letter. Then, make an appointment with me as soon as possible to discuss your learning needs.
Supporting video resources
- Floating point numbers
- Linear algebra refresher
- Eigenvalues
- Least squares
- Numerical time integration
- Numerical integration and quadrature
- Differential equations refresher


About
Automation Testing Engineer
- Phone:<PHONE_NUMBER>
- City: Brisbane
- Degree: Master of Information Technology
- Email<EMAIL_ADDRESS>I started my career as a software intern at Console Connect. During my internship, I learned how to create React applications and unit testing.
After my internship, the company offered me an automation engineer position due to the urgent need in quality engineer team. I accepted the challenge and decided to change my career direction. With React knowledge, I was able to learn Cypress efficiently and reduce regression efforts.
Cypress provides not only End to End regression within the company products but also verify the process of integrating with third party API such as AWS and google sdk.
I am passionate about automating both functional and non-functional tests, and am currently developing my skills in mobile automation and visual regression tests.
Skills
By combining manual testing and web development skill sets, It allows me to set up an automation framework that increases both the quality and productivity of the system.
Resume
Summary
Joe Jou
- Participant in numerous software community initiatives, including Brisbane Software Testers meetup and JavaScript Brisbane
- Testing experience across a range of front end and back-end applications involving a number of test automation tools and frameworks (Cucumber, Cypress, Postman), data analysis (SQL), and exploratory testing
- Experience with coding in JavaScript (Cypress, React)
- Certified SAFe 4 Practitioner
Education
Master of Information Technology (Computer Science)
2016 - 2018
Queensland University of Technology, Brisbane, Australia
Key Subjects
- Design thinking of IT
- Analysis of program
- Artificial intelligence
- Data mining.
Bachelor of Marine Biotechnology and Resources
2007 - 2011
National Sun Yat-sen University New Taipei City Taiwan (R.O.C.) Taiwan (R.O.C.)
Key Subjects
- Marine Biology
Professional Experience
Automation testing engineer
2022 - Present
Domain Group, Sydeny, Australia
- Test both iOS and Android apps to help the team deliver quality features
- Use quality assistance model to help developers improve product quality
Automation testing engineer
2019 - 2022 June
PCCW Global, Brisbane, Australia
- Developing automated tests for features within an agile team. These tests are critical for the delivery of these features.
- Contributing to exploratory testing of Console Connect to find defects before they impact customers
- Identifying and implementing web app improvements to better support automation.
- Establishing code review rules of automation projects and also sharing cypress knowledge across different feature teams
Software Engineer Intern
2019 Feb - 2019 Aug
PCCW Global, Brisbane, Australia
- Develop primarily on the customer facing front-end application by using JavaScript (React framework)
- Write and maintain unit tests and end to end tests to ensure high quality program standards are met
- Write and update technical documents in JIRA and GitHub to communicate with peer engineers and business stakeholders
- Improve and maintain existing software components by researching new methodology and refactoring existing code (ex: refactoring Angular form to React final form for better performance)
- Follow team code style and do code reviewing on version-control tool with GitHub and Git by using continuous integration tool (Jenkins)
- Attending chapter meetings to provide advice in developing software methodology
Github Side Projects
For the things we have to learn before we can do them, we learn by doing them
- All
- Web Development
- Automation Testing
Qualifications
ISTQB Fundamental Testing
The main outcome of this certification are understanding of fundamental concepts of software testing and how to use established techniques for designing tests at all test levels
Cypress Modern Automation Testing
Cypress for front end Web testing +Integration & API testing(XHR / API) with real time Frameworks (Mocha,Cucumber)
AWS Cloud Practitioner
The AWS Certified Cloud Practitioner examination is intended for individuals who have the knowledge and skills necessary to effectively demonstrate an overall understanding of the AWS Cloud


Writing a Basic Web Server
Mainly for the fun of it, I decided to look into what's involved in writing my own web server. I quickly became enthralled with the idea and the fascinating things I was discovering about browsers, compliance with browser expectations in the responses, and the sheer pleasure of writing a lean and mean web server. So, this article (as requested a few weeks ago by several people) describes the process. At this point, the implementation supports:
Because the web server is so lightweight, I found it completely unnecessary to do anything complicated like implementing plug-ins for the actual application. There's three core files:
I think it's important to state what this web server is not. What you won't find (and you may disagree with my severe criticisms, but this what I've learned over the last few years of web development):
Route
base class and
AuthenticatedRoute
child class. If you want role-based authentication,
it becomes a derived class rather than an attribute that decorates often
empty controller functions and that requires hoops to go through to
implement something different than what the framework designers decided
would be the right implementation. Throw that all away, because it
never works the way you want it, and it adds more performance bloat
constantly using reflection to check "oh, am I authorized", "oh, do I have
the right role for this?" Again, another example of horrid design.The ironic thing (and this is the last I'll say of it while standing on my soapbox) is that, having implemented my own web server, I've come to realize how much technologies like MVC Razor and Ruby on Rails (to pick two) get in the way of the business of serving web pages and giving the programmer control over how to render non-static content. With a simple web server, I find myself focusing much more on the client-side JavaScript, HTML, and components, where the only attention I have to pay to the server-side process is the PUT handlers and the occasional AJAX request. The baggage created by MVC, arcane routing syntaxes, attributes decorating unnecessary controller functions--well, the whole state of affairs of writing a web application is rather dismal, in my opinion.
The source code is hosted on GitHub:
git clone https://github.com/cliftonm/BasicWebServer.git
Rather than just showing you the final web server, I think it's much more interesting to document, step by step, the construction of the web server, so you can get a feel for how it is built from a blank slate, the design and implementation decisions (good or bad) and the overall process. I hope you, the reader, enjoy this approach.
The first step of process is to get an HttpListener
class working.
I opted to go this route rather than the lower level socket route because
HttpListener
provides a lot of useful services, such as decoding the HTML
request. I've read that it's not as performant as going the socket route,
but I'm not overly concerned with a little performance reduction.
The web server is implemented as a library. There is a console application for the specific web application executable. For this first step, the web server needs:
using System.Net; using System.Net.Sockets; using System.Threading;
Because a web server is primarily stateless (except for session objects) most of behaviors can be implemented as static singletons.
namespace Clifton.WebServer { ///  /// A lean and mean web server. ///  public static class Server { private static HttpListener listener; ...
We're going to make the initial assumption that we're connecting to the server on an intranet, so we obtain the IP's of our local host:
///  /// Returns list of IP addresses assigned to localhost network devices, such as hardwired ethernet, wireless, etc. ///  private static List<IPAddress> GetLocalHostIPs() { IPHostEntry host; host = Dns.GetHostEntry(Dns.GetHostName()); List<IPAddress> ret = host.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToList(); return ret; }
We then instantiate the HttpListener and add the localhost prefixes:
private static HttpListener InitializeListener(List<IPAddress> localhostIPs) { HttpListener listener = new HttpListener(); listener.Prefixes.Add("http://localhost/"); // Listen to IP address as well. localhostIPs.ForEach(ip => { Console.WriteLine("Listening on IP " + "http://" + ip.ToString() + "/"); listener.Prefixes.Add("http://" + ip.ToString() + "/"); }); return listener; }
You will probably have more than one localhost IP. For example, my laptop has an IP for both the ethernet and wireless "ports."
Borrowing a concept from Sacha's A Simple REST Framework, we'll set up a semaphore that waits for a specified number of simultaneously allowed connections:
public static int maxSimultaneousConnections = 20; private static Semaphore sem = new Semaphore(maxSimultaneousConnections, maxSimultaneousConnections);
This is implemented in a worker thread, which is invoked with Task.Run:
///  /// Begin listening to connections on a separate worker thread. ///  private static void Start(HttpListener listener) { listener.Start(); Task.Run(() => RunServer(listener)); }
///  /// Start awaiting for connections, up to the "maxSimultaneousConnections" value. /// This code runs in a separate thread. ///  private static void RunServer(HttpListener listener) { while (true) { sem.WaitOne(); StartConnectionListener(listener); } }
Lastly, we implement the connection listener as an awaitable asynchronous process:
///  /// Await connections. ///  private static async void StartConnectionListener(HttpListener listener) { // Wait for a connection. Return to caller while we wait. HttpListenerContext context = await listener.GetContextAsync(); // Release the semaphore so that another listener can be immediately started up. sem.Release(); // We have a connection, do something... }
So, let's do something:
string response = "Hello Browser!"; byte[] encoded = Encoding.UTF8.GetBytes(response); context.Response.ContentLength64 = encoded.Length; context.Response.OutputStream.Write(encoded, 0, encoded.Length); context.Response.OutputStream.Close();
And, we need a public Start
method:
///  /// Starts the web server. ///  public static void Start() { List<IPAddress> localHostIPs = GetLocalHostIPs(); HttpListener listener = InitializeListener(localHostIPs); Start(listener); }
Now in our console app, we can start up the server:
using System; using Clifton.WebServer; namespace ConsoleWebServer { class Program { static void Main(string[] args) { Server.Start(); Console.ReadLine(); } } }
and away we go:
The browser's web console window is your friend - it will tell you all the things you are doing wrong! For example, in our test case above, we discover:
Here we learn that we need to take care of the encoding, which is done in the HTML:
string response = "<meta http-equiv='content-type' content='text/html; charset=utf-8'/> Hello Browser!";
As this is just an example, we'll leave it at that for now. Later we'll learn more things we're doing wrong!
First, let's add some logging, as logging is really useful to see what kind of requests are being made of our web server:
Log(context.Request);
///  /// Log requests. ///  public static void Log(HttpListenerRequest request) { Console.WriteLine(request.RemoteEndPoint + " " + request.HttpMethod + " /" + request.Url.AbsoluteUri.RightOf('/', 3)); }
You can also use remote loggers, like PaperTrailApp, which I've written about here.
We add the Log call right after releasing the semaphore:
Log(context.Request);
Once we add the logging, we notice immediately that the browser not only requests the page at the default page, but it's also asking for favicon.ico!
Well, we need to something about that!
Obviously, we don't want to code our web pages as strings in C#. So, let's create a basic structure for our web application. This is completely arbitrary, but what I've chosen as a structure is that everything will derive from the folder "Website". Under "Website", we find the following folders:
To handle some basic functionality, we need the beginnings of a router. Our first cut will do nothing else than respond with files found in the "Webiste" folder and sub-folders as determined by the URL path and the request extension. We first extract some information from the URL request:
HttpListenerRequest request = context.Request; string path = request.RawUrl.LeftOf("?"); // Only the path, not any of the parameters string verb = request.HttpMethod; // get, post, delete, etc. string parms = request.RawUrl.RightOf("?"); // Params on the URL itself follow the URL and are separated by a ? Dictionary<string, string> kvParams = GetKeyValues(parms); // Extract into key-value entries.
We can now pass this information to the router:
router.Route(verb, path, kvParams);
Even though it could be static, there are some potential benefits to making
the router an actual instance, so we initialize it in the Server
class:
private static Router router = new Router();
Another nitpicky detail is the actual website path. Because I'm running the console program out of a bin\debug folder, the website path is actually "..\..\Website". Here's a clumsy way of getting this path:
public static string GetWebsitePath() { // Path of our exe. string websitePath = Assembly.GetExecutingAssembly().Location; websitePath = websitePath.LeftOfRightmostOf("\\").LeftOfRightmostOf("\\").LeftOfRightmostOf("\\") + "\\Website"; return websitePath; }
A little refactoring is need to We pass this in to the web server, which configures the router:
public static void Start(string websitePath) { router.WebsitePath = websitePath; ...
Since I don't particularly like switch statements, we'll initialize a map of known extensions and their loader locations. Consider how different functions could be used to load the content from, say, a database.
public class Router { public string WebsitePath { get; set; } private Dictionary<string, ExtensionInfo> extFolderMap; public Router() { extFolderMap = new Dictionary<string, ExtensionInfo>() { {"ico", new ExtensionInfo() {Loader=ImageLoader, ContentType="image/ico"}}, {"png", new ExtensionInfo() {Loader=ImageLoader, ContentType="image/png"}}, {"jpg", new ExtensionInfo() {Loader=ImageLoader, ContentType="image/jpg"}}, {"gif", new ExtensionInfo() {Loader=ImageLoader, ContentType="image/gif"}}, {"bmp", new ExtensionInfo() {Loader=ImageLoader, ContentType="image/bmp"}}, {"html", new ExtensionInfo() {Loader=PageLoader, ContentType="text/html"}}, {"css", new ExtensionInfo() {Loader=FileLoader, ContentType="text/css"}}, {"js", new ExtensionInfo() {Loader=FileLoader, ContentType="text/javascript"}}, {"", new ExtensionInfo() {Loader=PageLoader, ContentType="text/html"}}, }; } ....
Notice how we also handle the "no extension" case, which we implement as assuming that the content will be an HTML page.
Also notice that we set the content type. If we don't do this, we get a warning in the web console that content is assumed to be of a particular type.
Finally, notice that we're indicating a function for performing the actual loading.
///  /// Read in an image file and returns a ResponsePacket with the raw data. ///  private ResponsePacket ImageLoader(string fullPath, string ext, ExtensionInfo extInfo) { FileStream fStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read); BinaryReader br = new BinaryReader(fStream); ResponsePacket ret = new ResponsePacket() { Data = br.ReadBytes((int)fStream.Length), ContentType = extInfo.ContentType }; br.Close(); fStream.Close(); return ret; }
///  /// Read in what is basically a text file and return a ResponsePacket with the text UTF8 encoded. ///  private ResponsePacket FileLoader(string fullPath, string ext, ExtensionInfo extInfo) { string text = File.ReadAllText(fullPath); ResponsePacket ret = new ResponsePacket() { Data = Encoding.UTF8.GetBytes(text), ContentType = extInfo.ContentType, Encoding = Encoding.UTF8 }; return ret; }
The page loader has to do some fancy footwork to handle options like:
All of these combinations end up load Pages\index.html.
///  /// Load an HTML file, taking into account missing extensions and a file-less IP/domain, /// which should default to index.html. ///  private ResponsePacket PageLoader(string fullPath, string ext, ExtensionInfo extInfo) { ResponsePacket ret = new ResponsePacket(); if (fullPath == WebsitePath) // If nothing follows the domain name or IP, then default to loading index.html. { ret = Route(GET, "/index.html", null); } else { if (String.IsNullOrEmpty(ext)) { // No extension, so we make it ".html" fullPath = fullPath + ".html"; } // Inject the "Pages" folder into the path fullPath = WebsitePath + "\\Pages" + fullPath.RightOf(WebsitePath); ret = FileLoader(fullPath, ext, extInfo); } return ret; }
We have a couple helper classes:
public class ResponsePacket { public string Redirect { get; set; } public byte[] Data { get; set; } public string ContentType { get; set; } public Encoding Encoding { get; set; } } internal class ExtensionInfo { public string ContentType { get; set; } public Func<string, string, string, ExtensionInfo, ResponsePacket> Loader { get; set; } }
And after putting it all together, we have the beginnings of a router, which now returns content located in files.
public ResponsePacket Route(string verb, string path, Dictionary<string, string> kvParams) { string ext = path.RightOf('.'); ExtensionInfo extInfo; ResponsePacket ret = null; if (extFolderMap.TryGetValue(ext, out extInfo)) { // Strip off leading '/' and reformat as with windows path separator. string fullPath = Path.Combine(WebsitePath, path); ret = extInfo.Loader(fullPath, ext, extInfo); } return ret; }
We need one final refactoring -- removing our test response and replacing it with the content returned by the router:
private static void Respond(HttpListenerResponse response, ResponsePacket resp) { response.ContentType = resp.ContentType; response.ContentLength64 = resp.Data.Length; response.OutputStream.Write(resp.Data, 0, resp.Data.Length); response.ContentEncoding = resp.Encoding; response.StatusCode = (int)HttpStatusCode.OK; response.OutputStream.Close(); }
This is bare-bones implementation for now.
We can see this in action. Here's some HTML:
<!DOCTYPE html>   <meta charset="utf-8" />  <link type="text/css" rel="Stylesheet" href="/CSS/demo.css"/> <title>Button Click Demo</title>
 <input class="button" type="button" value="Click Me" id="me"/>

You can see the website structure:
And our little demo page works!
You can see several things going on here:
This is all fine and dandy, but:
These are all issues that we need to address, however, we can at this point create some pages with CSS and Javascript:, so, even though there's a lot of things to still work on, we do at this point have a lot working!
One of the things revealed here is how the actual location of the content "file" can be completely spoofed by the server. In the above code, I put all the HTML content under the folder Pages, thus spoofing the root location. We could do other things -- load data from a database, communicate with another server, generate the page dynamically from data...these are all features will explore as we move beyond default content loading.
Let's start dealing with the issues mentioned above.
We'll add several error pages, even though we're not using them all at the moment:
Now, you may wonder why the server knows things about expired sessions and authorization failures. Well, because it makes sense -- these errors are integral to the routing, but the error state is determined by the web application (not the server.) All the server does is query the web application for the state. More on this later.
We'd like the application to be determine where these pages are for the given error, so we'll add an enum to the server:
public enum ServerError { OK, ExpiredSession, NotAuthorized, FileNotFound, PageNotFound, ServerError, UnknownType, }
We can now begin to handle errors (without throwing exceptions). First off is an unknown extension:
if (extFolderMap.TryGetValue(ext, out extInfo)) { ... } else { ret = new ResponsePacket() { Error = Server.ServerError.UnknownType }; }
and so forth. We'll use a callback that the web application can provide for handling errors. This is in the form of the page to which the user should be redirected.
We then refactor our code to get, from the application, the page to display on error:
ResponsePacket resp = router.Route(verb, path, kvParams); if (resp.Error != ServerError.OK) { resp = router.Route("get", onError(resp.Error), null); } Respond(context.Response, resp);
and implement a straight forward error handler in the application:
public static string ErrorHandler(Server.ServerError error) { string ret = null; switch (error) { case Server.ServerError.ExpiredSession: ret= "/ErrorPages/expiredSession.html"; break; case Server.ServerError.FileNotFound: ret = "/ErrorPages/fileNotFound.html"; break; case Server.ServerError.NotAuthorized: ret = "/ErrorPages/notAuthorized.html"; break; case Server.ServerError.PageNotFound: ret = "/ErrorPages/pageNotFound.html"; break; case Server.ServerError.ServerError: ret = "/ErrorPages/serverError.html"; break; case Server.ServerError.UnknownType: ret = "/ErrorPages/unknownType.html"; break; } return ret; }
Of course, we have to initialize the error handler:
Server.onError = ErrorHandler;
We can now test a few things out. Of course, your application may want some more sophisticated messages!
You'll note that the URL in the above error messages hasn't changed to reflect the page. This is because we don't have response redirect working. Time to fix that:
We assume that the error handler will always redirect us to a different page,
so we change how we handle the response. Rather than getting a new
ResponsePacket
and sending that content back to the browser, we simply set the
Redirect
property to the page the web application wants us to go to. This
becomes, by the way, a universal redirect mechanism.)
if (resp.Error != ServerError.OK) { resp.Redirect = onError(resp.Error); }
and we do a little refactoring in the Resond
method:
private static void Respond(HttpListenerRequest request, HttpListenerResponse response, ResponsePacket resp) { if (String.IsNullOrEmpty(resp.Redirect)) { response.ContentType = resp.ContentType; response.ContentLength64 = resp.Data.Length; response.OutputStream.Write(resp.Data, 0, resp.Data.Length); response.ContentEncoding = resp.Encoding; response.StatusCode = (int)HttpStatusCode.OK; } else { response.StatusCode = (int)HttpStatusCode.Redirect; response.Redirect("http://" + request.UserHostAddress + resp.Redirect); } response.OutputStream.Close(); }
By the way, it's very important to close the output stream. If you don't, the browser can be left hanging, waiting for data.
Notice that since we're handling errors with redirects, the only two possible status codes our web server can respond with is OK and Redirect.
Now our redirecting is working:
We use the same redirect mechanism to catch actual exceptions by wrapping the
GetContextAsync
continuation in a try-catch block:
catch(Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); resp = new ResponsePacket() { Redirect = onError(ServerError.ServerError) }; }
Here's what a simulated error looks like:
Where are we?
Let's deal with verbs next, particularly POST verbs. This will allow us to tackle the next three live bullet items.
There are several verbs that can accompany an HTTP request:
Essentially, the web server doesn't really care about the verb -- all the verb does is provide additional information as to what handler to invoke for the response. Here we finally get to a topic I've avoided so far -- controllers. Rather than the web server having any cognizance of a Model-View-Controller pattern and/or enforcing such pattern on the web application developer, the web server I've implemented provides a simple mechanism for routing verbs and paths to handlers. That's all it needs to do. The handler, in turn, determines whether the browser should be redirected to a different page or stay on the current page. Behind the scenes, the handler can do other things, but from the perspective of the web server, that's all that the web server cares about.
We'll begin by adding a basic router. This consists of a Route
class:
public class Route { public string Verb { get; set; } public string Path { get; set; } public Func<Dictionary<string,string>, string> Action { get; set; } }
Notice the Action property, which is a callback function that passes in the URL parameters (we'll deal with post parameters in a bit) and expects an "optional" redirect URL.
We add a simple method to add routes to a route table:
public void AddRoute(Route route) { routes.Add(route); }
Now we can implement calling application specific handlers, which is a refactor of the Route method:
public ResponsePacket Route(string verb, string path, Dictionary<string, string> kvParams) { string ext = path.RightOfRightmostOf('.'); ExtensionInfo extInfo; ResponsePacket ret = null; verb = verb.ToLower(); if (extFolderMap.TryGetValue(ext, out extInfo)) { string wpath = path.Substring(1).Replace('/', '\\'); // Strip off leading '/' and reformat as with windows path separator. string fullPath = Path.Combine(WebsitePath, wpath); Route route = routes.SingleOrDefault(r => verb == r.Verb.ToLower() && path == r.Path); if (route != null) { // Application has a handler for this route. string redirect = route.Action(kvParams); if (String.IsNullOrEmpty(redirect)) { // Respond with default content loader. ret = extInfo.Loader(fullPath, ext, extInfo); } else { // Respond with redirect. ret = new ResponsePacket() { Redirect = redirect }; } } else { // Attempt default behavior ret = extInfo.Loader(fullPath, ext, extInfo); } } else { ret = new ResponsePacket() { Error = Server.ServerError.UnknownType }; } return ret; }
Now let's modify our demo page to make a POST call to the server when we click the button, and we'll redirect to a different page in our handler. Yes, I know this could be handled entirely in the Javascript, but we're demonstrating verb-path handlers here, so we'll implement this behavior on the server-side.
Let's also add processing the input stream of the request into key-value pairs as well, and add logging of the parameters (both in the URL and any parameters in the input stream) that is part of the request:
private static async void StartConnectionListener(HttpListener listener) { ... Dictionary<string, string> kvParams = GetKeyValues(parms); // Extract into key-value entries. string data = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding).ReadToEnd(); GetKeyValues(data, kvParams); Log(kvParams); ... } private static Dictionary<string, string> GetKeyValues(string data, Dictionary<string, string> kv = null) { kv.IfNull(() => kv = new Dictionary<string, string>()); data.If(d => d.Length > 0, (d) => d.Split('&').ForEach(keyValue => kv[keyValue.LeftOf('=')] = keyValue.RightOf('='))); return kv; } private static void Log(Dictionary<string, string> kv) { kv.ForEach(kvp=>Console.WriteLine(kvp.Key+" : "+kvp.Value)); }
It may be a bad practice to combine URL parameters and postback parameters into a single key-value pair collection, but we'll go with this "simpler" implementation for now.
We create a new HTML page /demo/redirect:
<!DOCTYPE html>   <meta charset="utf-8" /> <title>Redirect Demo</title> <link type="text/css" rel="Stylesheet" href="/CSS/demo.css"/>   <form name="myform" action="/demo/redirect" method="post">
 <input type="submit" class="button" value="Redirect Me" id='redirect' name="redirectButton" />
 </form>
And, without doing anything further, let's look at the trace log and the behavior when we click on the button:
First we see the GET verb as the page loads, then, clicking on the button, we see the POST with the parameters. The fun thing about writing your own web server is you really get a deeper sense of what is happening behind the scenes, something that is important for people who are new to web development. Note the following in relation to the HTML:
Now let's register a handler for this verb and path:
static void Main(string[] args) { string websitePath = GetWebsitePath(); Server.onError = ErrorHandler; // register a route handler: Server.AddRoute(new Route() { Verb = Router.POST, Path = "/demo/redirect", Action = RedirectMe }); Server.Start(websitePath); Console.ReadLine(); } public static string RedirectMe(Dictionary<string, string> parms) { return "/demo/clicked"; }
And now, when we click the button, we're redirected:
That was easy.
With a minimal amount of refactoring, we've take care of these three issues:
In step 5 above, I implemented a very basic route handler. What we'd like is something a little more sophisticated that can handle very common tasks:
We'll refactor the handler callbacks above to utilize a Routing class from which we can provide some built-in behaviors as well as allowing the web application developer to replace and/or add their own additional behaviors, such as role-based authentication.
First, let's add a basic Session
and SessionManager
class:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using Clifton.ExtensionMethods; namespace Clifton.WebServer { ///  /// Sessions are associated with the client IP. ///  public class Session { public DateTime LastConnection { get; set; } public bool Authorized { get; set; } ///  /// Can be used by controllers to add additional information that needs to persist in the session. ///  public Dictionary<string, string> Objects { get; set; } public Session() { Objects = new Dictionary<string, string>(); UpdateLastConnectionTime(); } public void UpdateLastConnectionTime() { LastConnection = DateTime.Now; } ///  /// Returns true if the last request exceeds the specified expiration time in seconds. ///  public bool IsExpired(int expirationInSeconds) { return (DateTime.Now - LastConnection).TotalSeconds > expirationInSeconds; } } public class SessionManager { ///  /// Track all sessions. ///  protected Dictionary<IPAddress, Session> sessionMap = new Dictionary<IPAddress, Session>(); // TODO: We need a way to remove very old sessions so that the server doesn't accumulate thousands of stale endpoints. public SessionManager() { sessionMap = new Dictionary<IPAddress, Session>(); } ///  /// Creates or returns the existing session for this remote endpoint. ///  public Session GetSession(IPEndPoint remoteEndPoint) { // The port is always changing on the remote endpoint, so we can only use IP portion. Session session = sessionMap.CreateOrGet(remoteEndPoint.Address); return session; } } }
The SessionManager
manages Session
instances associated with the client's
endpoint IP. Note the todo--that we need some way of removing sessions at
some point, otherwise this list will just keep growing! The Session class
contains a couple useful properties for managing the last connection date/time
as well as whether the user has been authorized (logged in, whatever) to view
"authorized" pages. We also provide a key-value pair dictionary for the
web application to persist "objects" associated with keys. Basic, but
functional.
Now, in our listener continuation, we can get the session associated with the endpoint IP:
private static async void StartConnectionListener(HttpListener listener) { ResponsePacket resp = null; // Wait for a connection. Return to caller while we wait. HttpListenerContext context = await listener.GetContextAsync();
Session session = sessionManager.GetSession(context.Request.RemoteEndPoint); ... resp = router.Route(verb, path, kvParams); // Update session last connection after getting the response, // as the router itself validates session expiration only on pages requiring authentication. session.UpdateLastConnectionTime();
That was quite easy! Note how we're updating the last connection time after giving the router (and our handlers) the option to first inspect the last session state.
Because session expiration is intimately associated with authorization, we
expect that when a session expires, the Authorized
flag will be cleared.
Now let's add some built-in functionality for checking authorization and session expiration. We'll add three classes to our server that the application can use:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Clifton.WebServer { ///  /// The base class for route handlers. ///  public abstract class RouteHandler { protected Func<Session, Dictionary<string, string>, string> handler; public RouteHandler(Func<Session, Dictionary<string, string>, string> handler) { this.handler = handler; } public abstract string Handle(Session session, Dictionary<string, string> parms); } ///  /// Page is always visible. ///  public class AnonymousRouteHandler : RouteHandler { public AnonymousRouteHandler(Func<Session, Dictionary<string, string>, string> handler) : base(handler) { } public override string Handle(Session session, Dictionary<string, string> parms) { return handler(session, parms); } } ///  /// Page is visible only to authorized users. ///  public class AuthenticatedRouteHandler : RouteHandler { public AuthenticatedRouteHandler(Func<Session, Dictionary<string, string>, string> handler) : base(handler) { } public override string Handle(Session session, Dictionary<string, string> parms) { string ret; if (session.Authorized) { ret = handler(session, parms); } else { ret = Server.onError(Server.ServerError.NotAuthorized); } return ret; } } ///  /// Page is visible only to authorized users whose session has not expired. ///  public class AuthenticatedExpirableRouteHandler : AuthenticatedRouteHandler { public AuthenticatedExpirableRouteHandler(Func<Session, Dictionary<string, string>, string> handler) : base(handler) { } public override string Handle(Session session, Dictionary<string, string> parms) { string ret; if (session.IsExpired(Server.expirationTimeSeconds)) { session.Authorized = false; ret = Server.onError(Server.ServerError.ExpiredSession); } else { ret = base.Handle(session, parms); } return ret; } } }
Notice that we also now pass the session instance to the handler. Convenient!
Next, we refactor the web application routing table to use the RouteHandler
derived classes. Our Route class is refactored:
public class Route { public string Verb { get; set; } public string Path { get; set; } public RouteHandler Handler { get; set; } }
The session is now passed in to the router and handed over to the route handler:
public ResponsePacket Route(Session session, string verb, string path, Dictionary<string, string> kvParams) { ... string redirect = route.Handler.Handle(session, kvParams); ...
Now we just need to update our web application by specifying the type of handler, for example:
Server.AddRoute(new Route() { Verb = Router.POST, Path = "/demo/redirect", Handler=new AnonymousRouteHandler(RedirectMe) });
and of course, our handler now receives the session instance:
public static string RedirectMe(Session session, Dictionary<string, string> parms) { return "/demo/clicked"; }
Let's create a route that requires authorization but the authorization flag is not set in the session:
Server.AddRoute(new Route() { Verb = Router.POST, Path = "/demo/redirect", Handler=new AuthenticatedRouteHandler(RedirectMe) });
We'll click on the "Redirect Me" button, and note that we get the "not authorized" page:
We'll do the same thing to test the expiration logic:
Server.AddRoute(new Route() { Verb = Router.POST, Path = "/demo/redirect", Handler=new AuthenticatedExpirableRouteHandler(RedirectMe) });
and after waiting 60 seconds (configurable in the Server) on the "Redirect Me" page:
While building a website, I find that authentication/expiration often gets in the way, so I like to spoof the authentication. We can do that by implementing onRequest, which the server calls if it exists:
public static Action<Session, HttpListenerContext> onRequest; ... // Wait for a connection. Return to caller while we wait. HttpListenerContext context = await listener.GetContextAsync(); Session session = sessionManager.GetSession(context.Request.RemoteEndPoint); onRequest.IfNotNull(r => r(session, context));
and we can implement our "always authorized and never expiring" session this way:
static void Main(string[] args) { string websitePath = GetWebsitePath(); Server.onError = ErrorHandler; // Never expire, always authorize Server.onRequest = (session, context) => { session.Authorized = true; session.UpdateLastConnectionTime(); };
Let's look at an AJAX callback to see if there's anything we need to do to handle that. We'll put together an HTML page with a simple AJAX jQuery script:
  <meta charset="utf-8" /> <title>AJAX Demo</title>  <link type="text/css" rel="Stylesheet" href="/CSS/demo.css"/>
 <input class="button" type="button" value="AJAX!" id="me"/>

We can see the request being made, but since we don't have a specific handler for this request, we see the server responding with the contents of the page, which is expected.
So let's register a route handler:
Server.AddRoute(new Route() { Verb = Router.PUT, Path = "/demo/ajax", Handler = new AnonymousRouteHandler(AjaxResponder) });
But now we have a problem. Our standard handler expects a redirect, not a data response:
public static string AjaxResponder(Session session, Dictionary<string, string> parms) { return "what???"; }
Yes, it's time for another refactoring. The handler needs finer control
over the response, and thus should return a ResponsePacket
, for
example:
public static ResponsePacket RedirectMe(Session session, Dictionary<string, string> parms) { return Server.Redirect("/demo/clicked"); } public static ResponsePacket AjaxResponder(Session session, Dictionary<string, string> parms) { string data = "You said " + parms["number"]; ResponsePacket ret = new ResponsePacket() { Data = Encoding.UTF8.GetBytes(data), ContentType = "text" }; return ret; }
This change required touching a few places where the handler response used to be a string. The most relevant piece of code changed was in the router itself:
Route handler = routes.SingleOrDefault(r => verb == r.Verb.ToLower() && path == r.Path); if (handler != null) { // Application has a handler for this route. ResponsePacket handlerResponse = handler.Handler.Handle(session, kvParams); if (handlerResponse == null) { // Respond with default content loader. ret = extInfo.Loader(session, fullPath, ext, extInfo); } else { // Respond with redirect. ret = handlerResponse; } }
but the change took all of about 5 minutes, and here's the result:
You can of course return the data in JSON or XML -- that is completely independent of the web server, but you are advised to set the content type correctly:
Also note that I used the "PUT" verb, which isn't necessarily appropriate, but I wanted to use it as an example. Look what happens if instead, we use the GET verb:
With the GET verb, the parameters are passed as part of the URL! Let's write a handler for this route:
Server.AddRoute(new Route() { Verb = Router.GET, Path = "/demo/ajax", Handler = new AnonymousRouteHandler(AjaxGetResponder) });
Note that we have to handle the GET verb with both no parameters (the browser's request) and with parameters:
Interestingly, we can use the browser now to test the GET response -- note the URL:
The underscore parameter is added by jQuery to get around Internet Explorer's caching, and is present only when cache is set to false and you're using the GET verb. Ignore it.
Testing a web server locally on a 192.168... IP address is fine, but what happens when you deploy the site? I did this using an Amazon EC2 server and discovered (obivously) that there is a local IP behind the firewall, vs. the public IP. You can see the same thing with your router. We can get the public IP with this code which I found on Stack Overflow (sorry, I the link to give proper credit):
public static string GetExternalIP() { string externalIP; externalIP = (new WebClient()).DownloadString("http://checkip.dyndns.org/"); externalIP = (new Regex(@"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")).Matches(externalIP)[0].ToString(); return externalIP; }
Possibly not the best approach, but it does work.
The salient thing here is that, when responding with a redirect, the public
IP must be used, not the UserHostAddress
:
if (String.IsNullOrEmpty(publicIP)) { response.Redirect("http://" + request.UserHostAddress + resp.Redirect); } else { response.Redirect("http://" + publicIP + resp.Redirect); }
Note that the above code for obtaining the external IP can be a bit slow, and should obviously only be done at server startup. Furthermore, it of course isn't necessary if you have an actual domain name. However, for testing your web application with a host provider without having registered a domain and pointing it to the host provider, the above step is absolutely necessary.
The above code is fine for testing when you don't have a registered domain
name, but obviously we don't want the user to see the IP address whenever we do
a redirect. I haven't testing this with an actual domain name, but the
guidance here is, simply set publicIP
with the actual domain name,
for example:
Server.publicIP="www.yourdomain.com";
As I said in the introduction, with the capabilities of jQuery, AJAX, Javascript and professional third party components, I can only rarely imagine the need for any complex server-side HTML generation using embedded Ruby or C# along with the markup itself. That said, there is one reason you probably want the server to modify the HTML, and that is to deal with CSRF attacks.
Here's a good explanation of CSRF and why you should care about it. However, do we need a runtime dynamic code compilation to spit out the necessary HTML? No, of course not. So, to deal with CSRF and more generally, server-side HTML manipulation, we'll add the ability for the web application to post-process the HTML before it is returned to the browser. We can do this in the router just before the HTML is encoded into a byte array:
string text = File.ReadAllText(fullPath); text = Server.postProcess(session, text); // post processing option, such as adding a validation token.
The default implementation provided by the server is:
public static string validationTokenScript = "<%AntiForgeryToken%>"; public static string validationTokenName = "__CSRFToken__"; private static string DefaultPostProcess(Session session, string html) { string ret = html.Replace(validationTokenScript, "<input name='" + validationTokenName + "' type='hidden' value='" + session.Objects[validationTokenName].ToString() + " id='#__csrf__'" + "/>"); return ret; }
Refactoring time! A token is created when a new session is encountered:
public Session GetSession(IPEndPoint remoteEndPoint) { Session session; if (!sessionMap.TryGetValue(remoteEndPoint.Address, out session)) { session=new Session(); session.Objects[Server.validationTokenName] = Guid.NewGuid().ToString(); sessionMap[remoteEndPoint.Address] = session; } return session; }
We can then, by default, implement a CSRF check on non-GET verbs (though we should probably be more selective than that, for the moment I'll just leave it at that):
public ResponsePacket Route(Session session, string verb, string path, Dictionary<string, string> kvParams) { string ext = path.RightOfRightmostOf('.'); ExtensionInfo extInfo; ResponsePacket ret = null; verb = verb.ToLower(); if (verb != GET) { if (!VerifyCSRF(session, kvParams)) { // Don't like multiple return points, but it's so convenient here! return Server.Redirect(Server.onError(Server.ServerError.ValidationError)); } } ... } ///  /// If a CSRF validation token exists, verify it matches our session value. /// If one doesn't exist, issue a warning to the console. ///  private bool VerifyCSRF(Session session, Dictionary<string,string> kvParams) { bool ret = true; string token; if (kvParams.TryGetValue(Server.validationTokenName, out token)) { ret = session.Objects[Server.validationTokenName].ToString() == token; } else { Console.WriteLine("Warning - CSRF token is missing. Consider adding it to the request."); } return ret; }
So, given this HTML:
<!DOCTYPE html>   <meta charset="utf-8" /> <title>Login</title> <link type="text/css" rel="Stylesheet" href="/CSS/demo.css"/>   <form name="myform" action="/demo/redirect" method="post"> <%AntiForgeryToken%>
 Username: <input name="username"/>

 Password: <input type="password" name="password"/>

 <input type="submit" value="Login"/>
 </form>
We can inspect the source and see our token, for example:
<form name="myform" action="/demo/redirect" method="post"> <input name='__CSRFToken__' type='hidden' value='a9161119-de6f-4bb2-8e21-8d089d556c37'/>
And in the console window, on the post, we see:
If we omit the validation token, we get a warning in the console window:
When you have finer grained control over the server, you can pretty much do anything you want in terms of inventing your own set of token replacements. You could even feed the HTML to different parsers. For example, I really like the Slim language template supported in Ruby on Rails. For example, in the Slim syntax, the login HTML looks like this:
doctype html html lang="en" xmlns="http://www.w3.org/1999/xhtml" head meta charset="utf-8" / title Login link href="/CSS/demo.css" rel="Stylesheet" type="text/css" / body form action="/demo/redirect" method="post" name="myform" | <%AntiForgeryToken% .center-inner.top-margin-50 | Username: input name="username" / .center-inner.top-margin-10 | Password: input name="password" type="password" / .center-inner.top-margin-10 input type="submit" value="Login" /
This is not available with ASP.NET, Razor, etc., and replacing the Razor parser engine is not trivial. However, we can easily add a Slim to HTML post-process parser to our web server.
Because of where we put this check in, we will get this warning on AJAX post/put/deletes as well, which is probably a good idea. Here's what our AJAX demo page looks like passing in the CSRF token:

This is probably not your typical implementation and it also results in some interesting browser behavior if the validation fails (sending a redirect as an AJAX response is a bit weird.) In any case, this becomes a rabbit hole that I don't want to pursue further and will leave it to the reader to decide whether AJAX requests should have a validation token. If you leave it off, then the console will simply issue a warning.
Websites should really use HTTPS nowadays, however I'm am going to leave this for another day, possibly a separate article or an addendum at some point to this article.
It would probably be nice to decode parameter values, for example, replacing "+" with whitespace and "%xx" with the appropriate actual character.
Post processing the HTML is one of those things ripe for chaining, and is on the todo list.
I'm sure there's other things that could be done!
What other major issues need to be taken care of? What horrendous mistakes did I make?
The idea is to keep the web server very small. I have a total of four classes (not including extension methods) and the whole thing is slightly less than 650 lines of code, comments and all.


K-Pop Star Goo Hara Found Dead: Lesser-Known Facts About Slain Singer
25 Nov 2019, 09:10 by Lubna Khan
K-Pop artist Goo Hara was found dead at her Seoul home on Sunday. The 28-year-old was one of the most prominent female stars and here are lesser-known facts about the South Korean star
Goo Hara became a member of girl group KARA, which she joined in 2008 after the departure of former member Kim Sung-hee (Image Source:- instagram/koohara__)
Goo Hara’s girl group KARA became the first ever female Korean act to perform at the Tokyo Dome in 2013 and the group was responsible for promoting K-pop in Japan (Image Source:- instagram/koohara__)
Goo Hara had filed a lawsuit against her boyfriend Choi Jong-bum in 2018 for uploading a nude photograph without her consent (Image Source:- instagram/koohara__)
On May 26, 2019, Goo Hara attempted suicide and had to be rushed to the hospital, after which she apologized to her fans (Image Source:- instagram/koohara__)
Goo Hara’s last Instagram post before her death read, ‘Goodnight’ (Image Source:- instagram/koohara__)
Goo Hara’s dead body was discovered less than two months after her fellow K-pop singer Sulli was discovered dead in her apartment (Image Source:- instagram/koohara__)


Hell and Silence is an EP by Las Vegas rock group , released in March 2010 in the United States. It was recorded at Battle Born Studios. All songs were written by Imagine Dragons and self-produced. The EP was in part mixed by Grammy nominated engineer Mark Needham. To promote the album the band performed five shows at SXSW 2010 including at the BMI Official Showcase.While at SXSW they were endorsed by Blue Microphones. They also toured the western United States with Nico Vega and Saint Motel. They also performed at Bite of Las Vegas Festival 2010, New Noise Music Festival, Neon Reverb Festival, and Fork Fest.
It was released on September 4, 2012 through Interscope Records. The extended track was released on February 12, 2013, adding three more songs. Recorded between 2010 and 2012, the album was primarily produced by the band themselves, as well as English hip-hop producer Alex da Kid and Brandon Darner from the American indie rock group The Envy Corps. It was mastered by Joe LaPorta. According to frontman Dan Reynolds, the album took three years to finish ...
Self-produced by members of the band along with English hip-hop producer Alexander Grant, known by his moniker Alex da Kid, the album was released by Interscope Records and Grant's KIDinaKORNER label on February 17, 2015, in the United States.
At Lollapalooza in Sao Paulo, Brazil, the last date on the Into the Night Tour, the band announced a rest, and complemented saying, "This is our last show for a while, and had no better place to end this tour".[51] The conclusion of the Into the Night Tour signaled the end of the Night Visions album cycle. Lead singer Dan Reynolds joked about the end of the Night Visions cycle, saying that "We're always writing on the road, [so] that second album will come, unless we die. next year.  I don't know when it will be, but it may come"
Buy onlineA soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
Tolman recruited longtime high school friend Daniel Wayne "Wing" Sermon, who had graduated from Berklee College of Music. Tolman later recruited his wife, Brittany Tolman, to sing backup and play keys.
Tolman recruited longtime high school friend Daniel Wayne "Wing" Sermon, who had graduated from Berklee College of Music. Tolman later recruited his wife, Brittany Tolman, to sing backup and play keys.
Tolman recruited longtime high school friend Daniel Wayne "Wing" Sermon, who had graduated from Berklee College of Music. Tolman later recruited his wife, Brittany Tolman, to sing backup and play keys.
Tolman recruited longtime high school friend Daniel Wayne "Wing" Sermon, who had graduated from Berklee College of Music. Tolman later recruited his wife, Brittany Tolman, to sing backup and play keys.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
A soulful singer/songwriter from a small English town with a penchant for crafting moving and evocative folk-pop confections in the vein of Ed Sheeran, Foy Vance, and Ben Howard, James Bay hails from the North Hertf... shire market town of Hitchin. Bay honed his skills regionally, eventually landing high-exposure support slots with Tom Odell, John Newman, and Kodaline before inking a record deal with Republic/Universal in 2012.
2-2-15 Hokukoryokuchi, Konohana Ward,554-0042 Osaka Prefecture Osaka Japan
When the last-minute request is for a focus group, it’s usually a sign that therequest originated in Marketing. When Web sites are being designed, the folks in Marketing often feel like they don’t have much clout. Even though they’re the ones who spend the most time trying to figure out who the site’s audience is and what they want, the designers and developers are the ones with most of the hands-on control over how the site actually gets put together.
In 2008, lead singer Dan Reynolds met drummer Andrew Tolman at Brigham Young University where they were both students.Tolman recruited longtime high school friend Daniel Wayne "Wing" Sermon, who had graduated from Berklee College of Music


by Paloma
About the Founder
Founder Paloma Jacome, born and raised in Venice Beach, California, is a self-taught artist. To honor the craftsmanship of embroidery, Paloma hand embroiders most of Pachamami's pieces. In addition to creating Pachamami products, Paloma is the creative director for Pachamami's content.
Company Mission
The Pachamama is of the highest divinity of the Andean people. Not only is she a protector of all, but she is the provider of plenty: fertility, the feminine, generosity, and ripening crops. The name Pachamama is translated from both Aymara and Quechua as "Mother Earth." We are all Mother Earth's children and she needs our help right now; our choices, lifestyles, and consumption all contribute to her health and longevity. Welcome to Patchamami: a collection of handpicked, handcrafted, and upcycled clothing! Pachamami is devoted to honoring the earth and committed to sustainability. As a slow fashion lifestyle brand, we extend the lives of existing textile materials including vintage pieces, fabric scraps, and discarded clothing. These donations are handcrafted into new designs. By being more intentional with our clothing choices, we can help abate the effects of fast fashion on our community, economy, and world.


shopper
shopify cart ui for custom frontends
shopper on github
price display:
collection:
single product:
cheers


In our community, we believe that every student is capable of contributing to everyone's learning experience with constructive discussion.
Could you take a moment to improve your input?
{{discussion?.body}}
{{discussion?.summary}}
by {{summaryAuthor?.name}} 0"> 👑 {{summaryMastery}}


Hello.
My name is Brian Storti, I'm an engineer at AlphaSights and previously at ThoughtWorks. Here I write about things I'm learning, usually related to distributed systems, programming languages and Unixy things.
I'm also the author of Kubernetes in Practice (and you can use the code blog for a discount).
You can follow me on twitter, read some of my code on github or send me an email.
And this is how I look eating cheese bread on a Sunday morning (very happy).
Cheers,


Approve
Start:
End:
Asset Name:
Close
Your browser does not support the audio tag.
Approve
Start:
End:
Asset Name:


Home
Information
Rules
Privacy Policy
Donation
Applications
Discord
Connect
Copyright © | Anarchyroleplay.com


Hi, I am RASEL a computer graduate & Full-Stack Web Developer specialized in Shopify and WordPress. I work with a team of Highly Experienced IT professionals and worked with a lot of Local Brands in the USA and I am still working with a lot of international WordPress Website, Shopify Stores at the moment. I do have the necessary techniques and knowledge to succeed! Let's get connected and let me help you make some money!


Polarity
: whether the word can be or is negated
In Slovenian, negation can be expressed in different ways. Syntactically, it can be marked by using negation particle ne “not”, as in Tega ne vem “I do not know that.” or Šli smo na ne najbolj zanimivo predavanje. “We attended a not too interesting lecture.”, or by using special negated verb form, as in To ni ona. “This is not her.” Morhplogically, negation is marked by prefix ne-, as in nepravičen “unfair”, neželen “unwanted” etc.
In the Slovenian UD Treebank, we currently only mark negation as an inflectional feature of a limited set of verbs and auxiliaries: biti “to be”, imeti “to have”, hoteti “to want”.
Neg
: negative
Polarity
is assigned to negated word forms of verbs biti, imeti, hoteti.
Examples
- nisem “I am not”, nisi “you are not”, ni “he/she/it is not” etc.
- nimam “I do not have”, nimaš “you do not have”, nima “he/she/it does not have” etc.
- nočem “I do not want”, nočeš “you do not want”, nočemo “he/she/it does not want” etc.
Pos
: positive, affirmative
Positive
is assigned to non-negated word forms of verbs biti, imeti, hoteti with identical set of other grammatical features.
Examples
- sem “I am”, nisi “you are”, je “he/she/it is” etc.
- imam “I have”, imaš “you have”, ima “he/she/it has” etc.
- hočem “I want”, hočeš “you want”, hočemo “he/she/it wants” etc.
Conversion from JOS
All verbs with feature Negative=no are converted to Negative=Pos
and all verbs with feature Negative=yes are converted to Negative=Neg
.
Treebank Statistics (UD_Slovenian)
This feature is language-specific.
It occurs with 2 different values: Neg
, Pos
.
9038 tokens (7%) have a non-empty value of Polarity
.
95 types (0%) occur at least once with a non-empty value of Polarity
.
5 lemmas (0%) occur at least once with a non-empty value of Polarity
.
The feature is used with 3 part-of-speech tags: sl-pos/AUX (7447; 6% instances), sl-pos/VERB (978; 1% instances), sl-pos/PART (613; 0% instances).
AUX
7447 sl-pos/AUX tokens (83% of all AUX
tokens) have a non-empty value of Polarity
.
The most frequent other feature values with which AUX
and Polarity
co-occurred: Mood=Ind (7447; 100%), VerbForm=Fin (7447; 100%), Tense=Pres (6559; 88%), Person=3 (6511; 87%), Number=Sing (5062; 68%).
AUX
tokens may have the following values of Polarity
:
Neg
(557; 7% of non-emptyPolarity
): ni, niso, nisem, nismo, niste, nista, nisva, nisiPos
(6890; 93% of non-emptyPolarity
): je, so, bo, sem, bodo, sta, smo, bomo, boste, ste
| Paradigm biti | Neg | Pos |
|---|---|---|
| Number=Sing|Person=1|Tense=Fut | bom | |
| Number=Sing|Person=1|Tense=Pres | nisem | sem |
| Number=Sing|Person=2|Tense=Fut | boš | |
| Number=Sing|Person=2|Tense=Pres | nisi | si, as |
| Number=Sing|Person=3|Tense=Fut | bo | |
| Number=Sing|Person=3|Tense=Pres | ni | je |
| Number=Dual|Person=1|Tense=Fut | bova | |
| Number=Dual|Person=1|Tense=Pres | nisva | sva |
| Number=Dual|Person=2|Tense=Fut | bosta | |
| Number=Dual|Person=2|Tense=Pres | sta | |
| Number=Dual|Person=3|Tense=Fut | bosta | |
| Number=Dual|Person=3|Tense=Pres | nista | sta |
| Number=Plur|Person=1|Tense=Fut | bomo | |
| Number=Plur|Person=1|Tense=Pres | nismo | smo |
| Number=Plur|Person=2|Tense=Fut | boste | |
| Number=Plur|Person=2|Tense=Pres | niste | ste |
| Number=Plur|Person=3|Tense=Fut | bodo, bojo | |
| Number=Plur|Person=3|Tense=Pres | niso | so |
VERB
978 sl-pos/VERB tokens (8% of all VERB
tokens) have a non-empty value of Polarity
.
The most frequent other feature values with which VERB
and Polarity
co-occurred: Gender=EMPTY (978; 100%), VerbForm=Fin (978; 100%), Mood=Ind (978; 100%), Tense=Pres (895; 92%), Person=3 (855; 87%), Number=Sing (726; 74%), Aspect=EMPTY (637; 65%).
VERB
tokens may have the following values of Polarity
:
Neg
(169; 17% of non-emptyPolarity
): ni, nima, nimajo, nimam, noče, nimamo, nisem, nimate, nismo, nisoPos
(809; 83% of non-emptyPolarity
): je, ima, imajo, bo, so, imamo, imate, imam, hoče, bodo
| Paradigm biti | Neg | Pos |
|---|---|---|
| Number=Sing|Person=1|Tense=Fut | bom | |
| Number=Sing|Person=1|Tense=Pres | nisem | sem |
| Number=Sing|Person=2|Tense=Pres | nisi | si |
| Number=Sing|Person=3|Tense=Fut | bo | |
| Number=Sing|Person=3|Tense=Pres | ni | je |
| Number=Dual|Person=1|Tense=Pres | Sva | |
| Number=Dual|Person=3|Tense=Pres | sta | |
| Number=Plur|Person=1|Tense=Pres | nismo | smo |
| Number=Plur|Person=2|Tense=Fut | boste | |
| Number=Plur|Person=2|Tense=Pres | niste | ste |
| Number=Plur|Person=3|Tense=Fut | bodo | |
| Number=Plur|Person=3|Tense=Pres | niso | so |
PART
613 sl-pos/PART tokens (17% of all PART
tokens) have a non-empty value of Polarity
.
PART
tokens may have the following values of Polarity
:
Neg
(613; 100% of non-emptyPolarity
): ne
Relations with Agreement in Polarity
The 10 most frequent relations where parent and child node agree in Polarity
:
AUX –[conj]–> VERB (1; 100%).
Treebank Statistics (UD_Slovenian-SST)
This feature is language-specific.
It occurs with 2 different values: Neg
, Pos
.
1783 tokens (9%) have a non-empty value of Polarity
.
43 types (1%) occur at least once with a non-empty value of Polarity
.
4 lemmas (0%) occur at least once with a non-empty value of Polarity
.
The feature is used with 3 part-of-speech tags: sl-pos/AUX (1090; 6% instances), sl-pos/VERB (499; 3% instances), sl-pos/PART (194; 1% instances).
AUX
1090 sl-pos/AUX tokens (86% of all AUX
tokens) have a non-empty value of Polarity
.
The most frequent other feature values with which AUX
and Polarity
co-occurred: VerbForm=Fin (1090; 100%), Mood=Ind (1090; 100%), Tense=Pres (939; 86%), Number=Sing (765; 70%), Person=3 (730; 67%).
AUX
tokens may have the following values of Polarity
:
Neg
(73; 7% of non-emptyPolarity
): ni, nisem, nismo, niso, nisi, niste, nisvaPos
(1017; 93% of non-emptyPolarity
): je, so, sem, smo, bo, si, bomo, ste, bom, boš
| Paradigm biti | Neg | Pos |
|---|---|---|
| Number=Sing|Person=1|Tense=Fut | bom | |
| Number=Sing|Person=1|Tense=Pres | nisem | sem |
| Number=Sing|Person=2|Tense=Fut | boš | |
| Number=Sing|Person=2|Tense=Pres | nisi | si |
| Number=Sing|Person=3|Tense=Fut | bo | |
| Number=Sing|Person=3|Tense=Pres | ni | je, biti |
| Number=Dual|Person=1|Tense=Fut | bova | |
| Number=Dual|Person=1|Tense=Pres | nisva | sva |
| Number=Dual|Person=3|Tense=Fut | bosta | |
| Number=Dual|Person=3|Tense=Pres | sta | |
| Number=Plur|Person=1|Tense=Fut | bomo | |
| Number=Plur|Person=1|Tense=Pres | nismo | smo |
| Number=Plur|Person=2|Tense=Fut | boste | |
| Number=Plur|Person=2|Tense=Pres | niste | ste |
| Number=Plur|Person=3|Tense=Fut | bodo, bojo | |
| Number=Plur|Person=3|Tense=Pres | niso | so |
VERB
499 sl-pos/VERB tokens (19% of all VERB
tokens) have a non-empty value of Polarity
.
The most frequent other feature values with which VERB
and Polarity
co-occurred: Mood=Ind (499; 100%), Gender=EMPTY (499; 100%), VerbForm=Fin (499; 100%), Tense=Pres (451; 90%), Number=Sing (379; 76%), Aspect=EMPTY (360; 72%), Person=3 (350; 70%).
VERB
tokens may have the following values of Polarity
:
Neg
(63; 13% of non-emptyPolarity
): ni, nima, nimam, nimajo, niso, noče, nimamo, nisem, nimaš, nismoPos
(436; 87% of non-emptyPolarity
): je, so, ima, bo, imamo, imajo, imaš, si, imate, bom
| Paradigm biti | Neg | Pos |
|---|---|---|
| Number=Sing|Person=1|Tense=Fut | bom | |
| Number=Sing|Person=1|Tense=Pres | nisem | sem |
| Number=Sing|Person=2|Tense=Fut | boš | |
| Number=Sing|Person=2|Tense=Pres | si | |
| Number=Sing|Person=3|Tense=Fut | bo | |
| Number=Sing|Person=3|Tense=Pres | ni | je |
| Number=Dual|Person=1|Tense=Fut | bova | |
| Number=Dual|Person=1|Tense=Pres | sva | |
| Number=Dual|Person=3|Tense=Fut | bosta | |
| Number=Dual|Person=3|Tense=Pres | sta | |
| Number=Plur|Person=1|Tense=Fut | bomo | |
| Number=Plur|Person=1|Tense=Pres | nismo | smo |
| Number=Plur|Person=2|Tense=Pres | niste | ste |
| Number=Plur|Person=3|Tense=Fut | bodo, bojo | |
| Number=Plur|Person=3|Tense=Pres | niso | so |
PART
194 sl-pos/PART tokens (11% of all PART
tokens) have a non-empty value of Polarity
.
PART
tokens may have the following values of Polarity
:
Neg
(194; 100% of non-emptyPolarity
): ne
Relations with Agreement in Polarity
The 10 most frequent relations where parent and child node agree in Polarity
:
VERB –[reparandum]–> VERB (10; 56%),
AUX –[reparandum]–> VERB (2; 100%),
AUX –[reparandum]–> AUX (1; 100%).
Polarity in other languages: [u]


Earn/Spend
Memo
Earn/Spend
Memo
Balance : 0
×
Add Earnings
Contents :
Date :
Amount :
Receipt :
Type
Contents
Date
Amount
Receipt